UNPKG

@ts-ast-parser/core

Version:

Reflects a simplified version of the TypeScript AST for generating documentation

282 lines 10.5 kB
import { createCommandLine } from './system/create-command-line.js'; import { AnalyserDiagnostic } from './analyser-diagnostic.js'; import { CompilerHost } from './system/compiler-host.js'; import { ProjectContext } from './project-context.js'; import { ModuleNode } from './nodes/module-node.js'; import ts from 'typescript'; /** * Represents a collection of modules * (TypeScript/JavaScript files) that have been * successfully analysed. */ export class Project { constructor(system, compilerHost, program, commandLine, options = {}) { Object.defineProperty(this, "_program", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_commandLine", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_fileNames", { enumerable: true, configurable: true, writable: true, value: new Set() }); Object.defineProperty(this, "_modules", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "_compilerHost", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_context", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_packageJson", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_diagnostics", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_system", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_options", { enumerable: true, configurable: true, writable: true, value: {} }); this._system = system; this._compilerHost = compilerHost; this._program = program; this._commandLine = commandLine; this._options = options; this._diagnostics = new AnalyserDiagnostic(system); this._packageJson = this._getPackageJSON(); this._context = new ProjectContext(system, () => this._program, () => this._commandLine, this._diagnostics, options); const commandLineErrors = ts.getConfigFileParsingDiagnostics(commandLine); this._diagnostics.addMany(commandLineErrors); this._diagnostics.addMany(program.getSyntacticDiagnostics()); this._diagnostics.addMany(program.getSemanticDiagnostics()); for (const fileName of program.getRootFileNames()) { const sourceFile = program.getSourceFile(fileName); if (!sourceFile) { this._diagnostics.addOne(sourceFile, `Unable to analyse file "${fileName}".`); continue; } this._fileNames.add(fileName); this._modules.push(new ModuleNode(sourceFile, this._context)); } } static fromTSConfig(system, options = {}) { const commandLine = createCommandLine(system, options); const compilerHost = new CompilerHost(system, commandLine.options); const program = ts.createProgram({ rootNames: commandLine.fileNames, options: commandLine.options, host: compilerHost, projectReferences: commandLine.projectReferences ?? [], }); return new Project(system, compilerHost, program, commandLine, options); } static fromFiles(system, files, options = {}) { const commandLine = createCommandLine(system, options); const compilerHost = new CompilerHost(system, commandLine.options); const program = ts.createProgram({ rootNames: files.map(f => system.getAbsolutePath(f)), options: commandLine.options, host: compilerHost, projectReferences: commandLine.projectReferences ?? [], }); return new Project(system, compilerHost, program, commandLine, options); } static fromSource(system, source, options = {}) { const fileName = 'unknown.ts'; system.writeFile(fileName, source); const commandLine = createCommandLine(system, options); const compilerHost = new CompilerHost(system, commandLine.options); const program = ts.createProgram({ rootNames: [system.getAbsolutePath(fileName)], options: commandLine.options, host: compilerHost, projectReferences: commandLine.projectReferences ?? [], }); return new Project(system, compilerHost, program, commandLine, options); } /** * An abstraction layer around how we interact with the environment (browser or Node.js) * * @returns The system environment used */ getSystem() { return this._system; } /** * A Program is an immutable collection of source files and the compiler options. Together * represent a compilation unit. * * @returns The TypeScript program created with the TypeScript compiler API */ getProgram() { return this._program; } /** * The TypeScript type checker. * * Useful to resolve the types and location of the reflected nodes. * * @returns The TypeScript type checker */ getTypeChecker() { return this._program.getTypeChecker(); } /** * The user provided analyzer options. * * @returns The options that were provided when calling the parser function */ getOptions() { return this._options; } /** * Here we save all the errors we find while analysing the source files * * @returns An instance of the `AnalyserDiagnostic` where all errors are enqueue */ getDiagnostics() { return this._diagnostics; } /** * All the reflected modules/files that have been successfully analysed. * * @returns The reflected modules */ getModules() { return this._modules; } /** * Whether the given source file path is present in the reflected modules * * @param filePath - The source file path to check * @returns True if the source file has been already analysed, false otherwise */ has(filePath) { const normalizedPath = this._system.normalizePath(filePath); return this._modules.some(m => m.getSourcePath() === normalizedPath); } /** * Adds a new source file to the collection of reflected modules. * * Will throw an error if the source file already exists in the program. * * @param filePath - The path of the new source file * @param content - The content of the source file * @returns The new reflected module */ add(filePath, content) { const normalizedPath = this._system.normalizePath(filePath); const absolutePath = this._system.getAbsolutePath(filePath); const module = this._modules.find(m => m.getSourcePath() === normalizedPath); if (module) { throw new Error('File already exist'); } const newSourceFile = this._upsertFile(absolutePath, content); const newModuleNode = new ModuleNode(newSourceFile, this._context); this._modules.push(newModuleNode); return newModuleNode; } /** * Updates the content of an existing source file. * * Will throw an error if the source file doesn't exist in the program. * * @param filePath - The path of the source file to update * @param content - The new content of the source file * @returns The updated reflected module */ update(filePath, content) { const normalizedPath = this._system.normalizePath(filePath); const absolutePath = this._system.getAbsolutePath(normalizedPath); const moduleIdx = this._modules.findIndex(m => m.getSourcePath() === normalizedPath); if (moduleIdx < 0) { throw new Error('The file doesn\'t exist'); } // FIXME(Jordi M.): We should update all the source files // that depend on the updated file const newSourceFile = this._upsertFile(absolutePath, content); const newModuleNode = new ModuleNode(newSourceFile, this._context); this._modules.splice(moduleIdx, 1, newModuleNode); return newModuleNode; } /** * The name of the package defined in the `package.json` in * case one was found * * @returns The package name if found, otherwise an empty string */ getName() { return this._packageJson?.name ?? ''; } /** * Serializes the project * * @returns The reflected node as a serializable object */ serialize() { return this._modules.map(m => m.serialize()); } _getPackageJSON() { const projectDir = this._options.tsConfigFilePath ? this._system.getDirectoryName(this._options.tsConfigFilePath) : this._system.getCurrentDirectory(); const packageDir = this._system.join(projectDir, 'package.json'); const pkgJSON = this._system.fileExists(packageDir) ? this._system.readFile(packageDir) : null; try { return pkgJSON != null ? JSON.parse(pkgJSON) : null; } catch (_) { return null; } } _upsertFile(fileName, data) { this._system.writeFile(fileName, data); if (!this._fileNames.has(fileName)) { this._commandLine = createCommandLine(this._system, this._options); this._fileNames.add(fileName); } this._program = ts.createProgram({ rootNames: [...this._fileNames], options: this._program.getCompilerOptions(), host: this._compilerHost, oldProgram: this._program, projectReferences: this._program.getProjectReferences() ?? [], }); return this._program.getSourceFile(fileName); } } //# sourceMappingURL=project.js.map