@esportsplus/typescript 0.28.1 → 0.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,11 @@ type CoordinatorResult = {
6
6
  sourceFile: ts.SourceFile;
7
7
  };
8
8
  declare const _default: {
9
- transform: (plugins: Plugin[], code: string, file: ts.SourceFile, program: ts.Program, shared: SharedContext) => CoordinatorResult;
9
+ transform: (plugins: Plugin[], code: string, file: ts.SourceFile, program: ts.Program, shared: SharedContext) => {
10
+ changed: boolean;
11
+ code: string;
12
+ sourceFile: ts.SourceFile;
13
+ };
10
14
  };
11
15
  export default _default;
12
16
  export type { CoordinatorResult };
@@ -1,6 +1,5 @@
1
1
  import { ts } from '../index.js';
2
2
  import imports from './imports.js';
3
- const DIRECTORY_SEPARATOR = /\\/g;
4
3
  function applyImports(code, file, intents) {
5
4
  for (let i = 0, n = intents.length; i < n; i++) {
6
5
  let intent = intents[i];
@@ -44,24 +43,6 @@ function applyPrepend(code, file, prepend) {
44
43
  }
45
44
  return code.slice(0, position) + prepend.join('\n') + code.slice(position);
46
45
  }
47
- function createUpdatedProgram(originalProgram, fileName, newCode) {
48
- let options = originalProgram.getCompilerOptions(), originalHost = ts.createCompilerHost(options), originalGetSourceFile = originalHost.getSourceFile.bind(originalHost), originalReadFile = originalHost.readFile.bind(originalHost);
49
- originalHost.getSourceFile = (name, languageVersion, onError, shouldCreateNewSourceFile) => {
50
- if (name === fileName ||
51
- name.replace(DIRECTORY_SEPARATOR, '/') === fileName.replace(DIRECTORY_SEPARATOR, '/')) {
52
- return ts.createSourceFile(name, newCode, languageVersion, true);
53
- }
54
- return originalGetSourceFile(name, languageVersion, onError, shouldCreateNewSourceFile);
55
- };
56
- originalHost.readFile = (name) => {
57
- if (name === fileName ||
58
- name.replace(DIRECTORY_SEPARATOR, '/') === fileName.replace(DIRECTORY_SEPARATOR, '/')) {
59
- return newCode;
60
- }
61
- return originalReadFile(name);
62
- };
63
- return ts.createProgram(originalProgram.getRootFileNames(), options, originalHost, originalProgram);
64
- }
65
46
  function hasPattern(code, patterns) {
66
47
  for (let i = 0, n = patterns.length; i < n; i++) {
67
48
  if (code.indexOf(patterns[i]) !== -1) {
@@ -136,42 +117,51 @@ const transform = (plugins, code, file, program, shared) => {
136
117
  if (plugins.length === 0) {
137
118
  return { changed: false, code, sourceFile: file };
138
119
  }
139
- let currentCode = code, currentFile = file, currentProgram = program, fileName = file.fileName, transformed = false;
120
+ let allImports = [], allPrepend = [], allReplacements = [], checker = program.getTypeChecker(), fileName = file.fileName, originalFile = file;
140
121
  for (let i = 0, n = plugins.length; i < n; i++) {
141
122
  let plugin = plugins[i];
142
- if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) {
123
+ if (plugin.patterns && !hasPattern(code, plugin.patterns)) {
143
124
  continue;
144
125
  }
145
126
  let { imports, prepend, replacements } = plugin.transform({
146
- checker: currentProgram.getTypeChecker(),
147
- code: currentCode,
148
- program: currentProgram,
127
+ checker,
128
+ code,
129
+ program,
149
130
  shared,
150
- sourceFile: currentFile
131
+ sourceFile: originalFile
151
132
  });
152
- let pluginChanged = false;
153
133
  if (replacements?.length) {
154
- currentCode = applyIntents(currentCode, currentFile, replacements);
155
- currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
156
- pluginChanged = true;
134
+ allReplacements.push(...replacements);
157
135
  }
158
136
  if (prepend?.length) {
159
- currentCode = applyPrepend(currentCode, currentFile, prepend);
160
- currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
161
- pluginChanged = true;
137
+ allPrepend.push(...prepend);
162
138
  }
163
139
  if (imports?.length) {
164
- currentCode = applyImports(currentCode, currentFile, imports);
165
- currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
166
- pluginChanged = true;
140
+ allImports.push(...imports);
167
141
  }
168
- if (pluginChanged) {
169
- transformed = true;
170
- currentProgram = createUpdatedProgram(currentProgram, fileName, currentCode);
142
+ }
143
+ let currentCode = code, currentFile = originalFile;
144
+ if (allReplacements.length > 0) {
145
+ allReplacements.sort((a, b) => a.node.getStart(originalFile) - b.node.getStart(originalFile));
146
+ for (let i = 1, n = allReplacements.length; i < n; i++) {
147
+ let prev = allReplacements[i - 1], curr = allReplacements[i], prevEnd = prev.node.end, currStart = curr.node.getStart(originalFile);
148
+ if (currStart < prevEnd) {
149
+ console.warn(`[coordinator] Overlapping replacements detected at ${fileName}:`, `[${prev.node.getStart(originalFile)}-${prevEnd}] overlaps [${currStart}-${curr.node.end}]`);
150
+ }
171
151
  }
152
+ currentCode = applyIntents(currentCode, currentFile, allReplacements);
153
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
154
+ }
155
+ if (allPrepend.length > 0) {
156
+ currentCode = applyPrepend(currentCode, currentFile, allPrepend);
157
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
158
+ }
159
+ if (allImports.length > 0) {
160
+ currentCode = applyImports(currentCode, currentFile, allImports);
161
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
172
162
  }
173
163
  return {
174
- changed: transformed,
164
+ changed: currentCode !== code,
175
165
  code: currentCode,
176
166
  sourceFile: currentFile
177
167
  };
@@ -3,6 +3,7 @@ import coordinator from '../coordinator.js';
3
3
  import program from '../program.js';
4
4
  const FILE_REGEX = /\.[tj]sx?$/;
5
5
  const DIRECTORY_SEPARATOR_REGEX = /\\/g;
6
+ const LINE_ENDINGS_REGEX = /\r\n/g;
6
7
  let contexts = new Map();
7
8
  export default ({ name, onWatchChange, plugins }) => {
8
9
  return ({ root } = {}) => {
@@ -18,7 +19,8 @@ export default ({ name, onWatchChange, plugins }) => {
18
19
  }
19
20
  try {
20
21
  let prog = program.get(root || ''), sourceFile = prog.getSourceFile(id.replace(DIRECTORY_SEPARATOR_REGEX, '/')) || prog.getSourceFile(id);
21
- if (!sourceFile || sourceFile.getText() !== code) {
22
+ if (!sourceFile ||
23
+ sourceFile.getText().replace(LINE_ENDINGS_REGEX, '\n') !== code.replace(LINE_ENDINGS_REGEX, '\n')) {
22
24
  sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true);
23
25
  }
24
26
  let result = coordinator.transform(plugins, code, sourceFile, prog, contexts.get(root || '') ?? contexts.set(root || '', new Map()).get(root || ''));
package/package.json CHANGED
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "type": "module",
41
41
  "types": "build/index.d.ts",
42
- "version": "0.28.1",
42
+ "version": "0.28.3",
43
43
  "scripts": {
44
44
  "build": "tsc && tsc-alias",
45
45
  "-": "-"
@@ -10,9 +10,6 @@ type CoordinatorResult = {
10
10
  };
11
11
 
12
12
 
13
- const DIRECTORY_SEPARATOR = /\\/g;
14
-
15
-
16
13
  function applyImports(code: string, file: ts.SourceFile, intents: ImportIntent[]): string {
17
14
  for (let i = 0, n = intents.length; i < n; i++) {
18
15
  let intent = intents[i];
@@ -76,51 +73,6 @@ function applyPrepend(code: string, file: ts.SourceFile, prepend: string[]): str
76
73
  return code.slice(0, position) + prepend.join('\n') + code.slice(position);
77
74
  }
78
75
 
79
- function createUpdatedProgram(
80
- originalProgram: ts.Program,
81
- fileName: string,
82
- newCode: string
83
- ): ts.Program {
84
- let options = originalProgram.getCompilerOptions(),
85
- originalHost = ts.createCompilerHost(options),
86
- originalGetSourceFile = originalHost.getSourceFile.bind(originalHost),
87
- originalReadFile = originalHost.readFile.bind(originalHost);
88
-
89
- originalHost.getSourceFile = (
90
- name: string,
91
- languageVersion: ts.ScriptTarget,
92
- onError?: (message: string) => void,
93
- shouldCreateNewSourceFile?: boolean
94
- ): ts.SourceFile | undefined => {
95
- if (
96
- name === fileName ||
97
- name.replace(DIRECTORY_SEPARATOR, '/') === fileName.replace(DIRECTORY_SEPARATOR, '/')
98
- ) {
99
- return ts.createSourceFile(name, newCode, languageVersion, true);
100
- }
101
-
102
- return originalGetSourceFile(name, languageVersion, onError, shouldCreateNewSourceFile);
103
- };
104
-
105
- originalHost.readFile = (name: string): string | undefined => {
106
- if (
107
- name === fileName ||
108
- name.replace(DIRECTORY_SEPARATOR, '/') === fileName.replace(DIRECTORY_SEPARATOR, '/')
109
- ) {
110
- return newCode;
111
- }
112
-
113
- return originalReadFile(name);
114
- };
115
-
116
- return ts.createProgram(
117
- originalProgram.getRootFileNames(),
118
- options,
119
- originalHost,
120
- originalProgram
121
- );
122
- }
123
-
124
76
  function hasPattern(code: string, patterns: string[]): boolean {
125
77
  for (let i = 0, n = patterns.length; i < n; i++) {
126
78
  if (code.indexOf(patterns[i]) !== -1) {
@@ -159,7 +111,6 @@ function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyO
159
111
  }
160
112
 
161
113
  let remove = options.remove ? new Set(options.remove) : null,
162
- // Collect all non-removed specifiers from existing imports
163
114
  specifiers = new Set<string>();
164
115
 
165
116
  for (let i = 0, n = found.length; i < n; i++) {
@@ -176,7 +127,6 @@ function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyO
176
127
  }
177
128
  }
178
129
 
179
- // Build replacement text - namespace import first, then named imports
180
130
  let statements: string[] = [];
181
131
 
182
132
  if (namespace) {
@@ -187,7 +137,6 @@ function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyO
187
137
  statements.push(`import { ${[...specifiers].sort().join(', ')} } from '${pkg}';`);
188
138
  }
189
139
 
190
- // Build replacements - replace first import, remove others
191
140
  let replacements: Replacement[] = [];
192
141
 
193
142
  for (let i = 0, n = found.length; i < n; i++) {
@@ -220,86 +169,96 @@ function replaceReverse(code: string, replacements: Replacement[]): string {
220
169
  };
221
170
 
222
171
 
223
- /**
224
- * Transform source through all plugins sequentially.
225
- * Each plugin receives fresh AST and TypeChecker with accurate positions.
226
- */
227
172
  const transform = (
228
173
  plugins: Plugin[],
229
174
  code: string,
230
175
  file: ts.SourceFile,
231
176
  program: ts.Program,
232
177
  shared: SharedContext
233
- ): CoordinatorResult => {
178
+ ) => {
234
179
  if (plugins.length === 0) {
235
180
  return { changed: false, code, sourceFile: file };
236
181
  }
237
182
 
238
- let currentCode = code,
239
- currentFile = file,
240
- currentProgram = program,
183
+ let allImports = [],
184
+ allPrepend = [],
185
+ allReplacements = [],
186
+ checker = program.getTypeChecker(),
241
187
  fileName = file.fileName,
242
- transformed = false;
188
+ // Keep original source file for symbol resolution - checker operations
189
+ // require nodes from a file that's part of the program's module graph
190
+ originalFile = file;
243
191
 
192
+ // Phase 1: Collect intents from all plugins using original source file
193
+ // This ensures checker.getSymbolAtLocation() and getAliasedSymbol() work
194
+ // correctly for re-exports and module resolution
244
195
  for (let i = 0, n = plugins.length; i < n; i++) {
245
196
  let plugin = plugins[i];
246
197
 
247
- if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) {
198
+ if (plugin.patterns && !hasPattern(code, plugin.patterns)) {
248
199
  continue;
249
200
  }
250
201
 
251
202
  let { imports, prepend, replacements } = plugin.transform({
252
- checker: currentProgram.getTypeChecker(),
253
- code: currentCode,
254
- program: currentProgram,
255
- shared,
256
- sourceFile: currentFile
257
- });
258
-
259
- let pluginChanged = false;
203
+ checker,
204
+ code,
205
+ program,
206
+ shared,
207
+ sourceFile: originalFile
208
+ });
260
209
 
261
210
  if (replacements?.length) {
262
- currentCode = applyIntents(currentCode, currentFile, replacements);
263
- currentFile = ts.createSourceFile(
264
- fileName,
265
- currentCode,
266
- currentFile.languageVersion,
267
- true
268
- );
269
- pluginChanged = true;
211
+ allReplacements.push(...replacements);
270
212
  }
271
213
 
272
214
  if (prepend?.length) {
273
- currentCode = applyPrepend(currentCode, currentFile, prepend);
274
- currentFile = ts.createSourceFile(
275
- fileName,
276
- currentCode,
277
- currentFile.languageVersion,
278
- true
279
- );
280
- pluginChanged = true;
215
+ allPrepend.push(...prepend);
281
216
  }
282
217
 
283
218
  if (imports?.length) {
284
- currentCode = applyImports(currentCode, currentFile, imports);
285
- currentFile = ts.createSourceFile(
286
- fileName,
287
- currentCode,
288
- currentFile.languageVersion,
289
- true
290
- );
291
- pluginChanged = true;
219
+ allImports.push(...imports);
292
220
  }
221
+ }
293
222
 
294
- // Rebuild program with updated source so next plugin gets valid checker
295
- if (pluginChanged) {
296
- transformed = true;
297
- currentProgram = createUpdatedProgram(currentProgram, fileName, currentCode);
223
+ // Phase 2: Apply all collected intents
224
+ let currentCode = code,
225
+ currentFile = originalFile;
226
+
227
+ if (allReplacements.length > 0) {
228
+ // Sort by start position to detect overlaps
229
+ allReplacements.sort((a, b) => a.node.getStart(originalFile) - b.node.getStart(originalFile));
230
+
231
+ // Check for overlapping replacements
232
+ for (let i = 1, n = allReplacements.length; i < n; i++) {
233
+ let prev = allReplacements[i - 1],
234
+ curr = allReplacements[i],
235
+ prevEnd = prev.node.end,
236
+ currStart = curr.node.getStart(originalFile);
237
+
238
+ if (currStart < prevEnd) {
239
+ console.warn(
240
+ `[coordinator] Overlapping replacements detected at ${fileName}:`,
241
+ `[${prev.node.getStart(originalFile)}-${prevEnd}] overlaps [${currStart}-${curr.node.end}]`
242
+ );
243
+ }
298
244
  }
245
+
246
+ currentCode = applyIntents(currentCode, currentFile, allReplacements);
247
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
248
+ }
249
+
250
+ if (allPrepend.length > 0) {
251
+ currentCode = applyPrepend(currentCode, currentFile, allPrepend);
252
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
253
+ }
254
+
255
+ if (allImports.length > 0) {
256
+ currentCode = applyImports(currentCode, currentFile, allImports);
257
+ currentFile = ts.createSourceFile(fileName, currentCode, currentFile.languageVersion, true);
299
258
  }
300
259
 
301
260
  return {
302
- changed: transformed,
261
+ changed: currentCode !== code,
303
262
  code: currentCode,
304
263
  sourceFile: currentFile
305
264
  };
@@ -307,4 +266,4 @@ const transform = (
307
266
 
308
267
 
309
268
  export default { transform };
310
- export type { CoordinatorResult };
269
+ export type { CoordinatorResult };
@@ -24,6 +24,8 @@ const FILE_REGEX = /\.[tj]sx?$/;
24
24
 
25
25
  const DIRECTORY_SEPARATOR_REGEX = /\\/g;
26
26
 
27
+ const LINE_ENDINGS_REGEX = /\r\n/g;
28
+
27
29
 
28
30
  let contexts = new Map<string, SharedContext>();
29
31
 
@@ -45,7 +47,10 @@ export default ({ name, onWatchChange, plugins }: VitePluginOptions) => {
45
47
  let prog = program.get(root || ''),
46
48
  sourceFile = prog.getSourceFile(id.replace(DIRECTORY_SEPARATOR_REGEX, '/')) || prog.getSourceFile(id);
47
49
 
48
- if (!sourceFile || sourceFile.getText() !== code) {
50
+ if (
51
+ !sourceFile ||
52
+ sourceFile.getText().replace(LINE_ENDINGS_REGEX, '\n') !== code.replace(LINE_ENDINGS_REGEX, '\n')
53
+ ) {
49
54
  sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true);
50
55
  }
51
56