@esportsplus/typescript
Advanced tools
@@ -46,2 +46,16 @@ import { ts } from '../index.js'; | ||
| } | ||
| function createPatchedProgram(baseProgram, fileName, newContent) { | ||
| let baseHost = ts.createCompilerHost(baseProgram.getCompilerOptions()), options = baseProgram.getCompilerOptions(); | ||
| return ts.createProgram(baseProgram.getRootFileNames(), options, { | ||
| ...baseHost, | ||
| getSourceFile: (name, languageVersion) => { | ||
| if (name === fileName || name === fileName.replace(/\\/g, '/')) { | ||
| return ts.createSourceFile(name, newContent, languageVersion, true); | ||
| } | ||
| return baseProgram.getSourceFile(name) ?? baseHost.getSourceFile(name, languageVersion); | ||
| }, | ||
| fileExists: (name) => baseHost.fileExists(name), | ||
| readFile: (name) => name === fileName ? newContent : baseHost.readFile(name) | ||
| }, baseProgram); | ||
| } | ||
| function hasPattern(code, patterns) { | ||
@@ -103,3 +117,2 @@ for (let i = 0, n = patterns.length; i < n; i++) { | ||
| } | ||
| ; | ||
| function replaceReverse(code, replacements) { | ||
@@ -117,3 +130,2 @@ if (replacements.length === 0) { | ||
| } | ||
| ; | ||
| const transform = (plugins, code, file, program, shared) => { | ||
@@ -123,51 +135,41 @@ if (plugins.length === 0) { | ||
| } | ||
| let allImports = [], allPrepend = [], allReplacements = [], checker = program.getTypeChecker(), fileName = file.fileName, originalFile = file; | ||
| let changed = false, currentCode = code, currentFile = file, currentProgram = program, fileName = file.fileName; | ||
| for (let i = 0, n = plugins.length; i < n; i++) { | ||
| let plugin = plugins[i]; | ||
| if (plugin.patterns && !hasPattern(code, plugin.patterns)) { | ||
| if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) { | ||
| continue; | ||
| } | ||
| let { imports, prepend, replacements } = plugin.transform({ | ||
| checker, | ||
| code, | ||
| program, | ||
| checker: currentProgram.getTypeChecker(), | ||
| code: currentCode, | ||
| program: currentProgram, | ||
| shared, | ||
| sourceFile: originalFile | ||
| }); | ||
| sourceFile: currentFile | ||
| }), pluginChanged = false; | ||
| if (replacements?.length) { | ||
| allReplacements.push(...replacements); | ||
| currentCode = applyIntents(currentCode, currentFile, replacements); | ||
| pluginChanged = true; | ||
| } | ||
| if (prepend?.length) { | ||
| allPrepend.push(...prepend); | ||
| currentCode = applyPrepend(currentCode, currentFile, prepend); | ||
| pluginChanged = true; | ||
| } | ||
| if (imports?.length) { | ||
| allImports.push(...imports); | ||
| currentCode = applyImports(currentCode, currentFile, imports); | ||
| pluginChanged = true; | ||
| } | ||
| } | ||
| let currentCode = code, currentFile = originalFile; | ||
| if (allReplacements.length > 0) { | ||
| allReplacements.sort((a, b) => a.node.getStart(originalFile) - b.node.getStart(originalFile)); | ||
| for (let i = 1, n = allReplacements.length; i < n; i++) { | ||
| let prev = allReplacements[i - 1], curr = allReplacements[i], prevEnd = prev.node.end, currStart = curr.node.getStart(originalFile); | ||
| if (currStart < prevEnd) { | ||
| console.warn(`[coordinator] Overlapping replacements detected at ${fileName}:`, `[${prev.node.getStart(originalFile)}-${prevEnd}] overlaps [${currStart}-${curr.node.end}]`); | ||
| if (pluginChanged) { | ||
| changed = true; | ||
| if (i < n - 1) { | ||
| currentProgram = createPatchedProgram(program, fileName, currentCode); | ||
| currentFile = currentProgram.getSourceFile(fileName) || | ||
| ts.createSourceFile(fileName, currentCode, file.languageVersion, true); | ||
| } | ||
| else { | ||
| currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true); | ||
| } | ||
| } | ||
| currentCode = applyIntents(currentCode, currentFile, allReplacements); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| if (allPrepend.length > 0) { | ||
| currentCode = applyPrepend(currentCode, currentFile, allPrepend); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| if (allImports.length > 0) { | ||
| currentCode = applyImports(currentCode, currentFile, allImports); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| return { | ||
| changed: currentCode !== code, | ||
| code: currentCode, | ||
| sourceFile: currentFile | ||
| }; | ||
| return { changed, code: currentCode, sourceFile: currentFile }; | ||
| }; | ||
| export default { transform }; |
+1
-1
@@ -42,3 +42,3 @@ { | ||
| "types": "build/index.d.ts", | ||
| "version": "0.28.3", | ||
| "version": "0.28.4", | ||
| "scripts": { | ||
@@ -45,0 +45,0 @@ "build": "tsc && tsc-alias", |
@@ -76,2 +76,24 @@ import type { ImportIntent, Plugin, Replacement, ReplacementIntent, SharedContext } from './types'; | ||
| function createPatchedProgram(baseProgram: ts.Program, fileName: string, newContent: string) { | ||
| let baseHost = ts.createCompilerHost(baseProgram.getCompilerOptions()), | ||
| options = baseProgram.getCompilerOptions(); | ||
| return ts.createProgram( | ||
| baseProgram.getRootFileNames(), | ||
| options, | ||
| { | ||
| ...baseHost, | ||
| getSourceFile: (name, languageVersion) => { | ||
| if (name === fileName || name === fileName.replace(/\\/g, '/')) { | ||
| return ts.createSourceFile(name, newContent, languageVersion, true); | ||
| } | ||
| return baseProgram.getSourceFile(name) ?? baseHost.getSourceFile(name, languageVersion); | ||
| }, | ||
| fileExists: (name) => baseHost.fileExists(name), | ||
| readFile: (name) => name === fileName ? newContent : baseHost.readFile(name) | ||
| }, | ||
| baseProgram | ||
| ); | ||
| } | ||
| function hasPattern(code: string, patterns: string[]): boolean { | ||
@@ -152,3 +174,3 @@ for (let i = 0, n = patterns.length; i < n; i++) { | ||
| return replaceReverse(code, replacements); | ||
| }; | ||
| } | ||
@@ -171,5 +193,4 @@ function replaceReverse(code: string, replacements: Replacement[]): string { | ||
| return result; | ||
| }; | ||
| } | ||
| const transform = ( | ||
@@ -186,18 +207,12 @@ plugins: Plugin[], | ||
| let allImports = [], | ||
| allPrepend = [], | ||
| allReplacements = [], | ||
| checker = program.getTypeChecker(), | ||
| fileName = file.fileName, | ||
| // Keep original source file for symbol resolution - checker operations | ||
| // require nodes from a file that's part of the program's module graph | ||
| originalFile = file; | ||
| let changed = false, | ||
| currentCode = code, | ||
| currentFile = file, | ||
| currentProgram = program, | ||
| fileName = file.fileName; | ||
| // Phase 1: Collect intents from all plugins using original source file | ||
| // This ensures checker.getSymbolAtLocation() and getAliasedSymbol() work | ||
| // correctly for re-exports and module resolution | ||
| for (let i = 0, n = plugins.length; i < n; i++) { | ||
| let plugin = plugins[i]; | ||
| if (plugin.patterns && !hasPattern(code, plugin.patterns)) { | ||
| if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) { | ||
| continue; | ||
@@ -207,64 +222,40 @@ } | ||
| let { imports, prepend, replacements } = plugin.transform({ | ||
| checker, | ||
| code, | ||
| program, | ||
| shared, | ||
| sourceFile: originalFile | ||
| }); | ||
| checker: currentProgram.getTypeChecker(), | ||
| code: currentCode, | ||
| program: currentProgram, | ||
| shared, | ||
| sourceFile: currentFile | ||
| }), | ||
| pluginChanged = false; | ||
| if (replacements?.length) { | ||
| allReplacements.push(...replacements); | ||
| currentCode = applyIntents(currentCode, currentFile, replacements); | ||
| pluginChanged = true; | ||
| } | ||
| if (prepend?.length) { | ||
| allPrepend.push(...prepend); | ||
| currentCode = applyPrepend(currentCode, currentFile, prepend); | ||
| pluginChanged = true; | ||
| } | ||
| if (imports?.length) { | ||
| allImports.push(...imports); | ||
| currentCode = applyImports(currentCode, currentFile, imports); | ||
| pluginChanged = true; | ||
| } | ||
| } | ||
| // Phase 2: Apply all collected intents | ||
| let currentCode = code, | ||
| currentFile = originalFile; | ||
| if (allReplacements.length > 0) { | ||
| // Sort by start position to detect overlaps | ||
| allReplacements.sort((a, b) => a.node.getStart(originalFile) - b.node.getStart(originalFile)); | ||
| // Check for overlapping replacements | ||
| for (let i = 1, n = allReplacements.length; i < n; i++) { | ||
| let prev = allReplacements[i - 1], | ||
| curr = allReplacements[i], | ||
| prevEnd = prev.node.end, | ||
| currStart = curr.node.getStart(originalFile); | ||
| if (currStart < prevEnd) { | ||
| console.warn( | ||
| `[coordinator] Overlapping replacements detected at ${fileName}:`, | ||
| `[${prev.node.getStart(originalFile)}-${prevEnd}] overlaps [${currStart}-${curr.node.end}]` | ||
| ); | ||
| if (pluginChanged) { | ||
| changed = true; | ||
| // Create patched program for next plugin | ||
| if (i < n - 1) { | ||
| currentProgram = createPatchedProgram(program, fileName, currentCode); | ||
| currentFile = currentProgram.getSourceFile(fileName) || | ||
| ts.createSourceFile(fileName, currentCode, file.languageVersion, true); | ||
| } | ||
| else { | ||
| currentFile = ts.createSourceFile(fileName, currentCode, file.languageVersion, true); | ||
| } | ||
| } | ||
| currentCode = applyIntents(currentCode, currentFile, allReplacements); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| if (allPrepend.length > 0) { | ||
| currentCode = applyPrepend(currentCode, currentFile, allPrepend); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| if (allImports.length > 0) { | ||
| currentCode = applyImports(currentCode, currentFile, allImports); | ||
| currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true); | ||
| } | ||
| return { | ||
| changed: currentCode !== code, | ||
| code: currentCode, | ||
| sourceFile: currentFile | ||
| }; | ||
| return { changed, code: currentCode, sourceFile: currentFile }; | ||
| }; | ||
@@ -271,0 +262,0 @@ |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
60910
-0.01%1539
-0.13%