diff --git a/projects/core/src/patch/patch-module.ts b/projects/core/src/patch/patch-module.ts index 7d16890..5627974 100644 --- a/projects/core/src/patch/patch-module.ts +++ b/projects/core/src/patch/patch-module.ts @@ -12,6 +12,7 @@ import { SourceSection } from '../module/source-section'; import { PatchError } from '../system'; import { readFileWithLock } from '../utils'; import { PatchDetail } from './patch-detail'; +import { patchSourceFile } from './transformers/sourcefile-parse'; /* ****************************************************************************************************************** */ @@ -66,7 +67,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js: /* Fix early return */ const typescriptSection = source.body.find(s => s.srcFileName === 'src/typescript/typescript.ts'); if (!typescriptSection) throw new PatchError(`Could not find Typescript source section`); - typescriptSection.transform([ fixTsEarlyReturnTransformer ]); + typescriptSection.transform([fixTsEarlyReturnTransformer]); printableBodyFooters.push(`return returnResult;`); } @@ -74,28 +75,33 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js: /* Patch Program */ const programSection = source.body.find(s => s.srcFileName === 'src/compiler/program.ts'); if (!programSection) throw new PatchError(`Could not find Program source section`); - programSection.transform([ patchCreateProgramTransformer ]); + programSection.transform([patchCreateProgramTransformer, patchSourceFile]); /* Add originalCreateProgram to exports */ const namespacesTsSection = source.body.find(s => s.srcFileName === 'src/typescript/_namespaces/ts.ts'); if (!namespacesTsSection) throw new PatchError(`Could not find NamespacesTs source section`); - namespacesTsSection.transform([ addOriginalCreateProgramTransformer ]); + namespacesTsSection.transform([addOriginalCreateProgramTransformer]); /* Patch emitter (for diagnostics tools) */ const emitterSection = source.body.find(s => s.srcFileName === 'src/compiler/watch.ts'); if (!emitterSection) throw new PatchError(`Could not find Emitter source section`); - emitterSection.transform([ patchEmitterTransformer ]); + emitterSection.transform([patchEmitterTransformer]); /* Move executeCommandLine outside of closure */ if (tsModule.moduleName === 'tsc.js') { const tscSection = source.body.find(s => s.srcFileName === 'src/tsc/tsc.ts'); if (!tscSection) throw new PatchError(`Could not find Tsc source section`); - tscSection.transform([ hookTscExecTransformer ]); + tscSection.transform([hookTscExecTransformer]); printableFooters.push(`tsp.${execTscCmd}();`); } + /* patch getSourceFile for incremental parser */ + const parserSection = source.body.find(s => s.srcFileName === 'src/compiler/parser.ts'); + if (!parserSection) throw new PatchError(`Could not find parser source section`); + parserSection.transform([patchSourceFile]); + /* Print the module */ const printedJs = printModule(); @@ -122,32 +128,32 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js: let indentLevel = 0; /* File Header */ - list.push([ source.fileHeader, indentLevel ]); + list.push([source.fileHeader, indentLevel]); /* Body Wrapper Open */ if (shouldWrap) { - list.push([ `\n${tsWrapperOpen}\n`, indentLevel ]); + list.push([`\n${tsWrapperOpen}\n`, indentLevel]); indentLevel = 2; } /* Body Header*/ - list.push([ source.bodyHeader, indentLevel ]); + list.push([source.bodyHeader, indentLevel]); /* Body */ - source.body.forEach(section => list.push([ section, indentLevel ])); + source.body.forEach(section => list.push([section, indentLevel])); /* Body Footers */ - printableBodyFooters.forEach(f => list.push([ f, indentLevel ])); + printableBodyFooters.forEach(f => list.push([f, indentLevel])); /* Body Wrapper Close */ if (shouldWrap) { indentLevel = 0; - list.push([ `\n${tsWrapperClose}\n`, indentLevel ]); + list.push([`\n${tsWrapperClose}\n`, indentLevel]); } /* File Footer */ - list.push([ source.fileFooter, indentLevel ]); - printableFooters.forEach(f => list.push([ f, indentLevel ])); + list.push([source.fileFooter, indentLevel]); + printableFooters.forEach(f => list.push([f, indentLevel])); return list; } @@ -156,7 +162,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js: const printer = ts.createPrinter(defaultNodePrinterOptions); let outputStr = ``; - for (const [ item, indentLevel ] of getPrintList()) { + for (const [item, indentLevel] of getPrintList()) { let printed: string; let addedIndent: number | undefined; if (item === undefined) continue; @@ -190,7 +196,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js: const addedSourceFile = addedSection.getSourceFile(); const transformer = createMergeStatementsTransformer(baseSourceFile, addedSourceFile); - baseSection.transform([ transformer ]); + baseSection.transform([transformer]); } } diff --git a/projects/core/src/patch/transformers/add-original-create-program.ts b/projects/core/src/patch/transformers/add-original-create-program.ts index f9ebabd..dde9d30 100644 --- a/projects/core/src/patch/transformers/add-original-create-program.ts +++ b/projects/core/src/patch/transformers/add-original-create-program.ts @@ -28,6 +28,18 @@ export function addOriginalCreateProgramTransformer(context: ts.TransformationCo ) { const exportObjectLiteral = node.expression.arguments[1]; if (ts.isObjectLiteralExpression(exportObjectLiteral)) { + const originalParseSourceFile = factory.createPropertyAssignment( + "originalParseSourceFile", + factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createIdentifier("originalParseSourceFile") + ) + ); + const originalCreateProgramProperty = factory.createPropertyAssignment( "originalCreateProgram", factory.createArrowFunction( @@ -42,7 +54,7 @@ export function addOriginalCreateProgramTransformer(context: ts.TransformationCo const updatedExportObjectLiteral = factory.updateObjectLiteralExpression( exportObjectLiteral, - [...exportObjectLiteral.properties, originalCreateProgramProperty] + [...exportObjectLiteral.properties, originalCreateProgramProperty, originalParseSourceFile] ); const updatedNode = factory.updateExpressionStatement( diff --git a/projects/core/src/patch/transformers/sourcefile-parse.ts b/projects/core/src/patch/transformers/sourcefile-parse.ts new file mode 100644 index 0000000..980db60 --- /dev/null +++ b/projects/core/src/patch/transformers/sourcefile-parse.ts @@ -0,0 +1,111 @@ +import ts from 'typescript'; + + +export function patchSourceFile(context: ts.TransformationContext) { + const { factory } = context; + + + return (sourceFile: ts.SourceFile) => { + + const getCompilerOptions = factory.createIdentifier('getCompilerOptions'); + const res = factory.updateSourceFile(sourceFile, ts.visitNodes(sourceFile.statements, visitNodes) as unknown as ts.Statement[]); + + return res; + + function visitNodes(node: ts.Node): ts.VisitResult { + if (ts.isFunctionDeclaration(node) && node.name && ( + node.name.escapedText === 'createSourceFile' || node.name.escapedText === 'updateSourceFile2' || node.name.escapedText === 'updateSourceFile' + )) { + const newParams = factory.createNodeArray([ + ...node.parameters, + factory.createParameterDeclaration( + undefined, + undefined, + getCompilerOptions, + undefined, + undefined, + )]) + + return ts.visitEachChild(factory.updateFunctionDeclaration( + node, + node.modifiers, + node.asteriskToken, + node.name, + node.typeParameters, + newParams, + node.type, + node.body + ), visitNodes, context); + } + + if (ts.isCallExpression(node) && (( + ts.isIdentifier(node.expression) && + node.expression.escapedText === 'createSourceFile' + ))) { + return factory.updateCallExpression(node, node.expression, node.typeArguments, + factory.createNodeArray([...node.arguments, factory.createNull(), getCompilerOptions]) + ); + } + + if ( + ts.isCallExpression(node) && (( + ts.isIdentifier(node.expression) && + node.expression.escapedText === 'parseSourceFile' + ) || ( + ts.isPropertyAccessExpression(node.expression) && + ts.isIdentifier(node.expression.expression) && + node.expression.expression.escapedText === 'Parser' && + node.expression.name.escapedText === "parseSourceFile" + ))) { + return factory.updateCallExpression(node, node.expression, node.typeArguments, + factory.createNodeArray([...node.arguments, getCompilerOptions]) + ); + } + if (ts.isFunctionDeclaration(node) && node.name && node.name.escapedText === 'parseSourceFile') { + const originalParseSourceFileId = factory.createIdentifier('originalParseSourceFile'); + const originalParseSourceFile = factory.updateFunctionDeclaration( + node, + node.modifiers, + node.asteriskToken, + originalParseSourceFileId, + node.typeParameters, + node.parameters, + node.type, + node.body + ); + const globalAsignment = factory.createAssignment( + factory.createPropertyAccessExpression(factory.createIdentifier("globalThis"), originalParseSourceFileId), + originalParseSourceFileId + ) + + const newParseSourceFile = factory.createFunctionDeclaration( + undefined, + undefined, + 'parseSourceFile', + undefined, + [], + undefined, + factory.createBlock([ + factory.createReturnStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('tsp'), + factory.createIdentifier('parseSourceFile') + ), + undefined, + [factory.createSpreadElement(factory.createIdentifier('arguments'))] + ) + ), + ]) + ); + + return [newParseSourceFile, originalParseSourceFile, globalAsignment] + } + + return ts.visitEachChild(node, visitNodes, context); + } + + }; +} + +// endregion diff --git a/projects/patch/src/ts/create-program.ts b/projects/patch/src/ts/create-program.ts index f889e9f..eafa460 100644 --- a/projects/patch/src/ts/create-program.ts +++ b/projects/patch/src/ts/create-program.ts @@ -1,16 +1,16 @@ namespace tsp { - const activeProgramTransformers = new Set(); + export const activeProgramTransformers = new Set(); const { dirname } = require('path'); /* ********************************************************* */ // region: Helpers /* ********************************************************* */ - function getProjectDir(compilerOptions: tsShim.CompilerOptions) { + export function getProjectDir(compilerOptions: tsShim.CompilerOptions) { return compilerOptions.configFilePath && dirname(compilerOptions.configFilePath); } - function getProjectConfig(compilerOptions: tsShim.CompilerOptions, rootFileNames: ReadonlyArray) { + export function getProjectConfig(compilerOptions: tsShim.CompilerOptions, rootFileNames: ReadonlyArray) { let configFilePath = compilerOptions.configFilePath; let projectDir = getProjectDir(compilerOptions); @@ -37,7 +37,7 @@ namespace tsp { return tsShim.parseJsonConfigFileContent(result.config, tsShim.sys, projectDir, undefined, configFileNamePath); } - function preparePluginsFromCompilerOptions(plugins: any): PluginConfig[] { + export function preparePluginsFromCompilerOptions(plugins: any): PluginConfig[] { if (!plugins) return []; // Old transformers system @@ -83,16 +83,16 @@ namespace tsp { /* Get Config */ const projectConfig = getProjectConfig(options, rootNames); - if ([ 'tsc', 'tsserver', 'tsserverlibrary' ].includes(tsp.currentLibrary)) { + if (['tsc', 'tsserver', 'tsserverlibrary'].includes(tsp.currentLibrary)) { options = projectConfig.compilerOptions; if (createOpts) createOpts.options = options; } /* Invoke TS createProgram */ let program: tsShim.Program & { originalEmit?: tsShim.Program['emit'] } = - createOpts ? - tsShim.originalCreateProgram(createOpts) : - tsShim.originalCreateProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics); + createOpts ? // @ts-ignore + tsShim.originalCreateProgram(createOpts) : // @ts-ignore + tsShim.originalCreateProgram(rootNames, options, host, oldProgram, configFileParsingDiagnostics); /* Prepare Plugins */ const plugins = preparePluginsFromCompilerOptions(options.plugins); @@ -102,7 +102,7 @@ namespace tsp { const programTransformers = pluginCreator.getProgramTransformers(); /* Transform Program */ - for (const [ transformerKey, [ programTransformer, config ] ] of programTransformers) { + for (const [transformerKey, [programTransformer, config]] of programTransformers) { if (activeProgramTransformers.has(transformerKey)) continue; activeProgramTransformers.add(transformerKey); diff --git a/projects/patch/src/ts/shim.ts b/projects/patch/src/ts/shim.ts index 9325565..93800c3 100644 --- a/projects/patch/src/ts/shim.ts +++ b/projects/patch/src/ts/shim.ts @@ -34,6 +34,8 @@ namespace tsp { export type CompilerHost = import('typescript').CompilerHost; export type Diagnostic = import('typescript').Diagnostic; export type SourceFile = import('typescript').SourceFile; + export type ScriptKind = import('typescript').ScriptKind; + export type ScriptTarget = import('typescript').ScriptTarget; export type WriteFileCallback = import('typescript').WriteFileCallback; export type CancellationToken = import('typescript').CancellationToken; export type CustomTransformers = import('typescript').CustomTransformers; diff --git a/projects/patch/src/ts/source-file.ts b/projects/patch/src/ts/source-file.ts new file mode 100644 index 0000000..7e257ef --- /dev/null +++ b/projects/patch/src/ts/source-file.ts @@ -0,0 +1,38 @@ + +namespace tsp { + + /* ********************************************************* * + * Patched parseSourceFile() + * ********************************************************* */ + + export function parseSourceFile( + fileName: string, + sourceText: string, + languageVersion: tsShim.ScriptTarget, + syntaxCursor: never | undefined, + setParentNodes = false, + scriptKind: tsShim.ScriptKind | undefined, + setExternalModuleIndicatorOverride: ((file: tsShim.SourceFile) => void) | undefined, + getCompilerOptions: () => tsShim.CompilerOptions + ): tsShim.SourceFile { + const options = getCompilerOptions(); + const projectDir = getProjectDir(options) + /* Get Config */ + + /* Invoke TS createProgram */ + // @ts-ignore + let file: tsShim.SourceFile = tsShim.originalParseSourceFile(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes, scriptKind, setExternalModuleIndicatorOverride) + + // /* Prepare Plugins */ + const plugins = preparePluginsFromCompilerOptions(options.plugins); + const pluginCreator = new PluginCreator(plugins, projectDir ?? process.cwd()); + + /* Prevent recursion in Program transformers */ + const transformers = pluginCreator.createTransformers({ program: new Error("Program not available for single source file") as never }) + + const transformed = tsShim.transform(file, transformers.before, options); + transformed.dispose(); + + return transformed.transformed[0] + } +}