@esportsplus/typescript 0.26.4 → 0.27.1

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.
Files changed (38) hide show
  1. package/build/cli/tsc.js +19 -4
  2. package/build/compiler/ast.d.ts +17 -0
  3. package/build/compiler/ast.js +42 -0
  4. package/build/compiler/coordinator.js +18 -21
  5. package/build/compiler/imports.d.ts +2 -2
  6. package/build/compiler/imports.js +28 -14
  7. package/build/compiler/index.d.ts +1 -1
  8. package/build/compiler/index.js +1 -1
  9. package/build/compiler/plugins/index.d.ts +1 -3
  10. package/build/compiler/plugins/tsc.d.ts +2 -5
  11. package/build/compiler/plugins/tsc.js +1 -11
  12. package/build/compiler/program.js +3 -2
  13. package/build/compiler/uid.d.ts +1 -1
  14. package/build/compiler/uid.js +4 -12
  15. package/build/constants.d.ts +2 -0
  16. package/build/constants.js +2 -0
  17. package/package.json +1 -1
  18. package/src/cli/tsc.ts +22 -4
  19. package/src/compiler/ast.ts +65 -0
  20. package/src/compiler/coordinator.ts +25 -32
  21. package/src/compiler/imports.ts +38 -21
  22. package/src/compiler/index.ts +2 -2
  23. package/src/compiler/plugins/tsc.ts +2 -20
  24. package/src/compiler/program.ts +3 -2
  25. package/src/compiler/uid.ts +5 -16
  26. package/src/constants.ts +4 -0
  27. package/build/compiler/ast/expression.d.ts +0 -4
  28. package/build/compiler/ast/expression.js +0 -23
  29. package/build/compiler/ast/index.d.ts +0 -3
  30. package/build/compiler/ast/index.js +0 -3
  31. package/build/compiler/ast/range.d.ts +0 -7
  32. package/build/compiler/ast/range.js +0 -10
  33. package/build/compiler/ast/visitor.d.ts +0 -3
  34. package/build/compiler/ast/visitor.js +0 -8
  35. package/src/compiler/ast/expression.ts +0 -34
  36. package/src/compiler/ast/index.ts +0 -3
  37. package/src/compiler/ast/range.ts +0 -21
  38. package/src/compiler/ast/visitor.ts +0 -13
package/build/cli/tsc.js CHANGED
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'url';
4
4
  import path from 'path';
5
5
  import ts from 'typescript';
6
6
  import coordinator from '../compiler/coordinator.js';
7
+ import { PACKAGE_NAME } from '../constants.js';
7
8
  const BACKSLASH_REGEX = /\\/g;
8
9
  let require = createRequire(import.meta.url), skipFlags = new Set(['--help', '--init', '--noEmit', '--showConfig', '--version', '-h', '-noEmit', '-v']);
9
10
  async function build(config, tsconfig, pluginConfigs) {
@@ -54,6 +55,9 @@ async function build(config, tsconfig, pluginConfigs) {
54
55
  }
55
56
  return runTscAlias(process.argv.slice(2)).then((code) => process.exit(code));
56
57
  }
58
+ function isPlugin(value) {
59
+ return typeof value === 'object' && value !== null && 'transform' in value && typeof value.transform === 'function';
60
+ }
57
61
  async function loadPlugins(configs, root) {
58
62
  let plugins = [], promises = [];
59
63
  for (let i = 0, n = configs.length; i < n; i++) {
@@ -69,8 +73,19 @@ async function loadPlugins(configs, root) {
69
73
  if (typeof plugin === 'function') {
70
74
  plugin = plugin();
71
75
  }
72
- if (!plugin || typeof plugin.transform !== 'function') {
73
- console.error(`Plugin ${config.transform}: invalid plugin format, expected { transform: Function }`);
76
+ if (Array.isArray(plugin)) {
77
+ for (let j = 0, m = plugin.length; j < m; j++) {
78
+ if (isPlugin(plugin[j])) {
79
+ plugins.push(plugin[j]);
80
+ }
81
+ else {
82
+ console.error(`${PACKAGE_NAME}: plugin ${config.transform}[${j}] uses an invalid plugin format`);
83
+ }
84
+ }
85
+ return;
86
+ }
87
+ if (!isPlugin(plugin)) {
88
+ console.error(`${PACKAGE_NAME}: plugin ${config.transform} uses an invalid plugin format, expected { transform: Function } or Plugin[]`);
74
89
  return;
75
90
  }
76
91
  plugins.push(plugin);
@@ -92,9 +107,9 @@ function main() {
92
107
  if (pluginConfigs.length === 0) {
93
108
  return passthrough();
94
109
  }
95
- console.log(`Found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
110
+ console.log(`${PACKAGE_NAME}: found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
96
111
  build(config, tsconfig, pluginConfigs).catch((err) => {
97
- console.error(err);
112
+ console.error(`${PACKAGE_NAME}: ${err}`);
98
113
  process.exit(1);
99
114
  });
100
115
  }
@@ -0,0 +1,17 @@
1
+ import ts from 'typescript';
2
+ type Range = {
3
+ end: number;
4
+ start: number;
5
+ };
6
+ declare const _default: {
7
+ expression: {
8
+ name: (node: ts.Expression) => string | null;
9
+ };
10
+ inRange: (ranges: Range[], start: number, end: number) => boolean;
11
+ property: {
12
+ path: (node: ts.Expression) => string | null;
13
+ };
14
+ test: (node: ts.Node, fn: (n: ts.Node) => boolean) => boolean;
15
+ };
16
+ export default _default;
17
+ export type { Range };
@@ -0,0 +1,42 @@
1
+ import ts from 'typescript';
2
+ const expression = {
3
+ name: (node) => {
4
+ if (ts.isIdentifier(node)) {
5
+ return node.text;
6
+ }
7
+ if (ts.isPropertyAccessExpression(node)) {
8
+ return property.path(node);
9
+ }
10
+ return null;
11
+ }
12
+ };
13
+ const inRange = (ranges, start, end) => {
14
+ for (let i = 0, n = ranges.length; i < n; i++) {
15
+ let r = ranges[i];
16
+ if (start >= r.start && end <= r.end) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ };
22
+ const property = {
23
+ path: (node) => {
24
+ let current = node, parts = [];
25
+ while (ts.isPropertyAccessExpression(current)) {
26
+ parts.push(current.name.text);
27
+ current = current.expression;
28
+ }
29
+ if (ts.isIdentifier(current)) {
30
+ parts.push(current.text);
31
+ return parts.reverse().join('.');
32
+ }
33
+ return null;
34
+ }
35
+ };
36
+ const test = (node, fn) => {
37
+ if (fn(node)) {
38
+ return true;
39
+ }
40
+ return !!ts.forEachChild(node, child => test(child, fn) || undefined);
41
+ };
42
+ export default { expression, inRange, property, test };
@@ -55,7 +55,7 @@ function modify(code, file, pkg, options) {
55
55
  if (!options.add && !options.namespace && !options.remove) {
56
56
  return code;
57
57
  }
58
- let { namespace } = options, add = options.add ? new Set(options.add) : null, found = imports.find(file, pkg);
58
+ let { namespace } = options, add = options.add ? new Set(options.add) : null, found = imports.all(file, pkg);
59
59
  if (found.length === 0) {
60
60
  let statements = [];
61
61
  if (namespace) {
@@ -117,39 +117,36 @@ const transform = (plugins, code, file, program, shared) => {
117
117
  if (plugins.length === 0) {
118
118
  return { changed: false, code, sourceFile: file };
119
119
  }
120
- let changed = false, currentCode = code, currentSourceFile = file;
120
+ let currentCode = code, currentFile = file;
121
121
  for (let i = 0, n = plugins.length; i < n; i++) {
122
122
  let plugin = plugins[i];
123
123
  if (plugin.patterns && !hasPattern(currentCode, plugin.patterns)) {
124
124
  continue;
125
125
  }
126
- let result = plugin.transform({
126
+ let { imports, prepend, replacements } = plugin.transform({
127
127
  checker: program.getTypeChecker(),
128
128
  code: currentCode,
129
129
  program,
130
130
  shared,
131
- sourceFile: currentSourceFile
131
+ sourceFile: currentFile
132
132
  });
133
- let hasChanges = (result.imports && result.imports.length > 0) ||
134
- (result.prepend && result.prepend.length > 0) ||
135
- (result.replacements && result.replacements.length > 0);
136
- if (!hasChanges) {
137
- continue;
138
- }
139
- changed = true;
140
- if (result.replacements && result.replacements.length > 0) {
141
- currentCode = applyIntents(currentCode, currentSourceFile, result.replacements);
142
- currentSourceFile = ts.createSourceFile(currentSourceFile.fileName, currentCode, currentSourceFile.languageVersion, true);
133
+ if (replacements?.length) {
134
+ currentCode = applyIntents(currentCode, currentFile, replacements);
135
+ currentFile = ts.createSourceFile(currentFile.fileName, currentCode, currentFile.languageVersion, true);
143
136
  }
144
- if (result.prepend && result.prepend.length > 0) {
145
- currentCode = applyPrepend(currentCode, currentSourceFile, result.prepend);
146
- currentSourceFile = ts.createSourceFile(currentSourceFile.fileName, currentCode, currentSourceFile.languageVersion, true);
137
+ if (prepend?.length) {
138
+ currentCode = applyPrepend(currentCode, currentFile, prepend);
139
+ currentFile = ts.createSourceFile(currentFile.fileName, currentCode, currentFile.languageVersion, true);
147
140
  }
148
- if (result.imports && result.imports.length > 0) {
149
- currentCode = applyImports(currentCode, currentSourceFile, result.imports);
150
- currentSourceFile = ts.createSourceFile(currentSourceFile.fileName, currentCode, currentSourceFile.languageVersion, true);
141
+ if (imports?.length) {
142
+ currentCode = applyImports(currentCode, currentFile, imports);
143
+ currentFile = ts.createSourceFile(currentFile.fileName, currentCode, currentFile.languageVersion, true);
151
144
  }
152
145
  }
153
- return { changed, code: currentCode, sourceFile: currentSourceFile };
146
+ return {
147
+ changed: currentCode !== code,
148
+ code: currentCode,
149
+ sourceFile: currentFile
150
+ };
154
151
  };
155
152
  export default { transform };
@@ -10,8 +10,8 @@ type ModifyOptions = {
10
10
  remove?: Iterable<string>;
11
11
  };
12
12
  declare const _default: {
13
- find: (sourceFile: ts.SourceFile, packageName: string) => ImportInfo[];
14
- inPackage: (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolName?: string, packageImports?: Set<string>) => boolean;
13
+ all: (file: ts.SourceFile, pkg: string) => ImportInfo[];
14
+ includes: (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolName?: string) => boolean;
15
15
  };
16
16
  export default _default;
17
17
  export type { ImportInfo, ModifyOptions };
@@ -1,13 +1,14 @@
1
1
  import { ts } from '../index.js';
2
- const find = (sourceFile, packageName) => {
2
+ let cache = new WeakMap();
3
+ const all = (file, pkg) => {
3
4
  let imports = [];
4
- for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
5
- let stmt = sourceFile.statements[i];
5
+ for (let i = 0, n = file.statements.length; i < n; i++) {
6
+ let stmt = file.statements[i];
6
7
  if (!ts.isImportDeclaration(stmt)) {
7
8
  continue;
8
9
  }
9
10
  let moduleSpecifier = stmt.moduleSpecifier;
10
- if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName) {
11
+ if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== pkg) {
11
12
  continue;
12
13
  }
13
14
  let bindings = stmt.importClause?.namedBindings, specifiers = new Map();
@@ -17,19 +18,32 @@ const find = (sourceFile, packageName) => {
17
18
  specifiers.set(propertyName, name);
18
19
  }
19
20
  }
20
- imports.push({ end: stmt.end, specifiers, start: stmt.getStart(sourceFile) });
21
+ imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file) });
21
22
  }
22
23
  return imports;
23
24
  };
24
- const inPackage = (checker, node, pkg, symbolName, packageImports) => {
25
- if (packageImports && ts.isIdentifier(node) && packageImports.has(node.text)) {
26
- if (!symbolName || node.text === symbolName) {
27
- return true;
25
+ const includes = (checker, node, pkg, symbolName) => {
26
+ let file = node.getSourceFile(), imports = cache.get(file);
27
+ if (!imports) {
28
+ imports = new Map();
29
+ cache.set(file, imports);
30
+ }
31
+ let varnames = imports.get(pkg);
32
+ if (!varnames) {
33
+ varnames = new Set();
34
+ for (let info of all(file, pkg)) {
35
+ for (let [, varname] of info.specifiers) {
36
+ varnames.add(varname);
37
+ }
28
38
  }
39
+ imports.set(pkg, varnames);
40
+ }
41
+ if (ts.isIdentifier(node) && varnames.has(node.text) && (!symbolName || node.text === symbolName)) {
42
+ return true;
29
43
  }
30
44
  let symbol = checker.getSymbolAtLocation(node);
31
45
  if (!symbol) {
32
- if (packageImports && ts.isIdentifier(node) && packageImports.has(node.text)) {
46
+ if (ts.isIdentifier(node) && varnames.has(node.text)) {
33
47
  return true;
34
48
  }
35
49
  return false;
@@ -38,17 +52,17 @@ const inPackage = (checker, node, pkg, symbolName, packageImports) => {
38
52
  symbol = checker.getAliasedSymbol(symbol);
39
53
  }
40
54
  if (symbolName && symbol.name !== symbolName) {
41
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
55
+ return ts.isIdentifier(node) && varnames.has(node.text);
42
56
  }
43
57
  let declarations = symbol.getDeclarations();
44
58
  if (!declarations || declarations.length === 0) {
45
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
59
+ return ts.isIdentifier(node) && varnames.has(node.text);
46
60
  }
47
61
  for (let i = 0, n = declarations.length; i < n; i++) {
48
62
  if (declarations[i].getSourceFile().fileName.includes(pkg)) {
49
63
  return true;
50
64
  }
51
65
  }
52
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
66
+ return ts.isIdentifier(node) && varnames.has(node.text);
53
67
  };
54
- export default { find, inPackage };
68
+ export default { all, includes };
@@ -1,4 +1,4 @@
1
- export * as ast from './ast/index.js';
1
+ export { default as ast } from './ast.js';
2
2
  export { default as code } from './code.js';
3
3
  export { default as coordinator } from './coordinator.js';
4
4
  export { default as imports } from './imports.js';
@@ -1,4 +1,4 @@
1
- export * as ast from './ast/index.js';
1
+ export { default as ast } from './ast.js';
2
2
  export { default as code } from './code.js';
3
3
  export { default as coordinator } from './coordinator.js';
4
4
  export { default as imports } from './imports.js';
@@ -1,7 +1,5 @@
1
1
  declare const _default: {
2
- tsc: (plugins: import("../index.js").Plugin[]) => (program: import("typescript").Program, shared: import("../index.js").SharedContext) => {
3
- transform: import("typescript").TransformerFactory<import("typescript").SourceFile>;
4
- };
2
+ tsc: (plugins: import("../index.js").Plugin[]) => () => import("../index.js").Plugin[];
5
3
  vite: ({ name, onWatchChange, plugins }: {
6
4
  name: string;
7
5
  onWatchChange?: () => void;
@@ -1,6 +1,3 @@
1
- import type { Plugin, SharedContext } from '../types.js';
2
- import type ts from 'typescript';
3
- declare const _default: (plugins: Plugin[]) => (program: ts.Program, shared: SharedContext) => {
4
- transform: ts.TransformerFactory<ts.SourceFile>;
5
- };
1
+ import type { Plugin } from '../types.js';
2
+ declare const _default: (plugins: Plugin[]) => () => Plugin[];
6
3
  export default _default;
@@ -1,13 +1,3 @@
1
- import coordinator from '../coordinator.js';
2
1
  export default (plugins) => {
3
- return (program, shared) => {
4
- return {
5
- transform: (() => {
6
- return (sourceFile) => {
7
- let result = coordinator.transform(plugins, sourceFile.getFullText(), sourceFile, program, shared);
8
- return result.changed ? result.sourceFile : sourceFile;
9
- };
10
- })
11
- };
12
- };
2
+ return () => plugins;
13
3
  };
@@ -1,5 +1,6 @@
1
1
  import path from 'path';
2
2
  import ts from 'typescript';
3
+ import { PACKAGE_NAME } from '../constants.js';
3
4
  let cache = new Map();
4
5
  function create(root) {
5
6
  let tsconfig = ts.findConfigFile(root, ts.sys.fileExists, 'tsconfig.json');
@@ -8,11 +9,11 @@ function create(root) {
8
9
  }
9
10
  let file = ts.readConfigFile(tsconfig, ts.sys.readFile);
10
11
  if (file.error) {
11
- throw new Error(`Error reading tsconfig.json: ${file.error.messageText}`);
12
+ throw new Error(`${PACKAGE_NAME}: error reading tsconfig.json ${file.error.messageText}`);
12
13
  }
13
14
  let parsed = ts.parseJsonConfigFileContent(file.config, ts.sys, path.dirname(tsconfig));
14
15
  if (parsed.errors.length > 0) {
15
- throw new Error(`Error parsing tsconfig.json: ${parsed.errors[0].messageText}`);
16
+ throw new Error(`${PACKAGE_NAME}: error parsing tsconfig.json ${parsed.errors[0].messageText}`);
16
17
  }
17
18
  return ts.createProgram({
18
19
  options: parsed.options,
@@ -1,2 +1,2 @@
1
- declare const _default: (prefix: string) => string;
1
+ declare const _default: (name: string) => string;
2
2
  export default _default;
@@ -1,13 +1,5 @@
1
- const INVALID_CHARS = /[^A-Za-z0-9]/g;
2
- let counter = 0;
3
- function hash(str) {
4
- let h = 0x811c9dc5;
5
- for (let i = 0, n = str.length; i < n; i++) {
6
- h ^= str.charCodeAt(i);
7
- h = Math.imul(h, 0x01000193);
8
- }
9
- return ((h >>> 0).toString(36) + Math.abs(h).toString(36)).replace(INVALID_CHARS, '');
10
- }
11
- export default (prefix) => {
12
- return prefix + '_' + hash(prefix) + (counter++).toString(36);
1
+ import { uuid } from '@esportsplus/utilities';
2
+ let i = 0, namespace = uuid().replace(/[^A-Za-z0-9]/g, '');
3
+ export default (name) => {
4
+ return name + '_' + namespace + (i++).toString(36);
13
5
  };
@@ -0,0 +1,2 @@
1
+ declare const PACKAGE_NAME = "@esportsplus/typescript";
2
+ export { PACKAGE_NAME };
@@ -0,0 +1,2 @@
1
+ const PACKAGE_NAME = '@esportsplus/typescript';
2
+ export { PACKAGE_NAME };
package/package.json CHANGED
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "type": "module",
39
39
  "types": "build/index.d.ts",
40
- "version": "0.26.4",
40
+ "version": "0.27.1",
41
41
  "scripts": {
42
42
  "build": "tsc && tsc-alias",
43
43
  "-": "-"
package/src/cli/tsc.ts CHANGED
@@ -5,6 +5,7 @@ import path from 'path';
5
5
  import ts from 'typescript';
6
6
  import coordinator from '~/compiler/coordinator';
7
7
  import type { Plugin, SharedContext } from '~/compiler/types';
8
+ import { PACKAGE_NAME } from '~/constants';
8
9
 
9
10
 
10
11
  type PluginConfig = {
@@ -109,6 +110,10 @@ async function build(config: object, tsconfig: string, pluginConfigs: PluginConf
109
110
  return runTscAlias(process.argv.slice(2)).then((code) => process.exit(code));
110
111
  }
111
112
 
113
+ function isPlugin(value: unknown): value is Plugin {
114
+ return typeof value === 'object' && value !== null && 'transform' in value && typeof (value as Plugin).transform === 'function';
115
+ }
116
+
112
117
  async function loadPlugins(configs: PluginConfig[], root: string): Promise<Plugin[]> {
113
118
  let plugins: Plugin[] = [],
114
119
  promises: Promise<void>[] = [];
@@ -132,8 +137,21 @@ async function loadPlugins(configs: PluginConfig[], root: string): Promise<Plugi
132
137
  plugin = plugin();
133
138
  }
134
139
 
135
- if (!plugin || typeof plugin.transform !== 'function') {
136
- console.error(`Plugin ${config.transform}: invalid plugin format, expected { transform: Function }`);
140
+ if (Array.isArray(plugin)) {
141
+ for (let j = 0, m = plugin.length; j < m; j++) {
142
+ if (isPlugin(plugin[j])) {
143
+ plugins.push(plugin[j]);
144
+ }
145
+ else {
146
+ console.error(`${PACKAGE_NAME}: plugin ${config.transform}[${j}] uses an invalid plugin format`);
147
+ }
148
+ }
149
+
150
+ return;
151
+ }
152
+
153
+ if (!isPlugin(plugin)) {
154
+ console.error(`${PACKAGE_NAME}: plugin ${config.transform} uses an invalid plugin format, expected { transform: Function } or Plugin[]`);
137
155
  return;
138
156
  }
139
157
 
@@ -168,10 +186,10 @@ function main(): void {
168
186
  return passthrough();
169
187
  }
170
188
 
171
- console.log(`Found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
189
+ console.log(`${PACKAGE_NAME}: found ${pluginConfigs.length} transformer plugin(s), using coordinated build...`);
172
190
 
173
191
  build(config, tsconfig, pluginConfigs).catch((err) => {
174
- console.error(err);
192
+ console.error(`${PACKAGE_NAME}: ${err}`);
175
193
  process.exit(1);
176
194
  });
177
195
  }
@@ -0,0 +1,65 @@
1
+ import ts from 'typescript';
2
+
3
+
4
+ type Range = {
5
+ end: number;
6
+ start: number;
7
+ };
8
+
9
+
10
+ const expression = {
11
+ name: (node: ts.Expression): string | null => {
12
+ if (ts.isIdentifier(node)) {
13
+ return node.text;
14
+ }
15
+
16
+ if (ts.isPropertyAccessExpression(node)) {
17
+ return property.path(node);
18
+ }
19
+
20
+ return null;
21
+ }
22
+ };
23
+
24
+ const inRange = (ranges: Range[], start: number, end: number): boolean => {
25
+ for (let i = 0, n = ranges.length; i < n; i++) {
26
+ let r = ranges[i];
27
+
28
+ if (start >= r.start && end <= r.end) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ return false;
34
+ };
35
+
36
+ const property = {
37
+ path: (node: ts.Expression): string | null => {
38
+ let current: ts.Node = node,
39
+ parts: string[] = [];
40
+
41
+ while (ts.isPropertyAccessExpression(current)) {
42
+ parts.push(current.name.text);
43
+ current = current.expression;
44
+ }
45
+
46
+ if (ts.isIdentifier(current)) {
47
+ parts.push(current.text);
48
+ return parts.reverse().join('.');
49
+ }
50
+
51
+ return null;
52
+ }
53
+ };
54
+
55
+ const test = (node: ts.Node, fn: (n: ts.Node) => boolean): boolean => {
56
+ if (fn(node)) {
57
+ return true;
58
+ }
59
+
60
+ return !!ts.forEachChild(node, child => test(child, fn) || undefined);
61
+ };
62
+
63
+
64
+ export default { expression, inRange, property, test };
65
+ export type { Range };
@@ -90,7 +90,7 @@ function modify(code: string, file: ts.SourceFile, pkg: string, options: ModifyO
90
90
 
91
91
  let { namespace } = options,
92
92
  add = options.add ? new Set(options.add) : null,
93
- found = imports.find(file, pkg);
93
+ found = imports.all(file, pkg);
94
94
 
95
95
  if (found.length === 0) {
96
96
  let statements: string[] = [];
@@ -187,9 +187,8 @@ const transform = (
187
187
  return { changed: false, code, sourceFile: file };
188
188
  }
189
189
 
190
- let changed = false,
191
- currentCode = code,
192
- currentSourceFile = file;
190
+ let currentCode = code,
191
+ currentFile = file;
193
192
 
194
193
  for (let i = 0, n = plugins.length; i < n; i++) {
195
194
  let plugin = plugins[i];
@@ -198,56 +197,50 @@ const transform = (
198
197
  continue;
199
198
  }
200
199
 
201
- let result = plugin.transform({
200
+ let { imports, prepend, replacements } = plugin.transform({
202
201
  checker: program.getTypeChecker(),
203
202
  code: currentCode,
204
203
  program,
205
204
  shared,
206
- sourceFile: currentSourceFile
205
+ sourceFile: currentFile
207
206
  });
208
207
 
209
- let hasChanges = (result.imports && result.imports.length > 0) ||
210
- (result.prepend && result.prepend.length > 0) ||
211
- (result.replacements && result.replacements.length > 0);
212
-
213
- if (!hasChanges) {
214
- continue;
215
- }
216
-
217
- changed = true;
218
-
219
- if (result.replacements && result.replacements.length > 0) {
220
- currentCode = applyIntents(currentCode, currentSourceFile, result.replacements);
221
- currentSourceFile = ts.createSourceFile(
222
- currentSourceFile.fileName,
208
+ if (replacements?.length) {
209
+ currentCode = applyIntents(currentCode, currentFile, replacements);
210
+ currentFile = ts.createSourceFile(
211
+ currentFile.fileName,
223
212
  currentCode,
224
- currentSourceFile.languageVersion,
213
+ currentFile.languageVersion,
225
214
  true
226
215
  );
227
216
  }
228
217
 
229
- if (result.prepend && result.prepend.length > 0) {
230
- currentCode = applyPrepend(currentCode, currentSourceFile, result.prepend);
231
- currentSourceFile = ts.createSourceFile(
232
- currentSourceFile.fileName,
218
+ if (prepend?.length) {
219
+ currentCode = applyPrepend(currentCode, currentFile, prepend);
220
+ currentFile = ts.createSourceFile(
221
+ currentFile.fileName,
233
222
  currentCode,
234
- currentSourceFile.languageVersion,
223
+ currentFile.languageVersion,
235
224
  true
236
225
  );
237
226
  }
238
227
 
239
- if (result.imports && result.imports.length > 0) {
240
- currentCode = applyImports(currentCode, currentSourceFile, result.imports);
241
- currentSourceFile = ts.createSourceFile(
242
- currentSourceFile.fileName,
228
+ if (imports?.length) {
229
+ currentCode = applyImports(currentCode, currentFile, imports);
230
+ currentFile = ts.createSourceFile(
231
+ currentFile.fileName,
243
232
  currentCode,
244
- currentSourceFile.languageVersion,
233
+ currentFile.languageVersion,
245
234
  true
246
235
  );
247
236
  }
248
237
  }
249
238
 
250
- return { changed, code: currentCode, sourceFile: currentSourceFile };
239
+ return {
240
+ changed: currentCode !== code,
241
+ code: currentCode,
242
+ sourceFile: currentFile
243
+ };
251
244
  };
252
245
 
253
246
 
@@ -14,12 +14,15 @@ type ModifyOptions = {
14
14
  };
15
15
 
16
16
 
17
+ let cache = new WeakMap<ts.SourceFile, Map<string, Set<string>>>();
18
+
19
+
17
20
  // Find all named imports from a specific package
18
- const find = (sourceFile: ts.SourceFile, packageName: string): ImportInfo[] => {
21
+ const all = (file: ts.SourceFile, pkg: string): ImportInfo[] => {
19
22
  let imports: ImportInfo[] = [];
20
23
 
21
- for (let i = 0, n = sourceFile.statements.length; i < n; i++) {
22
- let stmt = sourceFile.statements[i];
24
+ for (let i = 0, n = file.statements.length; i < n; i++) {
25
+ let stmt = file.statements[i];
23
26
 
24
27
  if (!ts.isImportDeclaration(stmt)) {
25
28
  continue;
@@ -27,7 +30,7 @@ const find = (sourceFile: ts.SourceFile, packageName: string): ImportInfo[] => {
27
30
 
28
31
  let moduleSpecifier = stmt.moduleSpecifier;
29
32
 
30
- if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== packageName) {
33
+ if (!ts.isStringLiteral(moduleSpecifier) || moduleSpecifier.text !== pkg) {
31
34
  continue;
32
35
  }
33
36
 
@@ -44,32 +47,46 @@ const find = (sourceFile: ts.SourceFile, packageName: string): ImportInfo[] => {
44
47
  }
45
48
  }
46
49
 
47
- imports.push({ end: stmt.end, specifiers, start: stmt.getStart(sourceFile) });
50
+ imports.push({ end: stmt.end, specifiers, start: stmt.getStart(file) });
48
51
  }
49
52
 
50
53
  return imports;
51
54
  };
52
55
 
53
56
  // Check if node's symbol originates from a specific package (with optional symbol name validation)
54
- const inPackage = (
55
- checker: ts.TypeChecker,
56
- node: ts.Node,
57
- pkg: string,
58
- symbolName?: string,
59
- packageImports?: Set<string>
60
- ): boolean => {
61
- // Fast path: identifier matches known import and expected name
62
- if (packageImports && ts.isIdentifier(node) && packageImports.has(node.text)) {
63
- if (!symbolName || node.text === symbolName) {
64
- return true;
57
+ const includes = (checker: ts.TypeChecker, node: ts.Node, pkg: string, symbolName?: string): boolean => {
58
+ let file = node.getSourceFile(),
59
+ imports = cache.get(file);
60
+
61
+ if (!imports) {
62
+ imports = new Map();
63
+ cache.set(file, imports);
64
+ }
65
+
66
+ let varnames = imports.get(pkg);
67
+
68
+ if (!varnames) {
69
+ varnames = new Set();
70
+
71
+ for (let info of all(file, pkg)) {
72
+ for (let [, varname] of info.specifiers) {
73
+ varnames.add(varname);
74
+ }
65
75
  }
76
+
77
+ imports.set(pkg, varnames);
78
+ }
79
+
80
+ // Fast path: identifier matches known import and expected name
81
+ if (ts.isIdentifier(node) && varnames.has(node.text) && (!symbolName || node.text === symbolName)) {
82
+ return true;
66
83
  }
67
84
 
68
85
  let symbol = checker.getSymbolAtLocation(node);
69
86
 
70
87
  if (!symbol) {
71
88
  // Fallback: aliased import - check if local name is in imports
72
- if (packageImports && ts.isIdentifier(node) && packageImports.has(node.text)) {
89
+ if (ts.isIdentifier(node) && varnames.has(node.text)) {
73
90
  return true;
74
91
  }
75
92
 
@@ -83,13 +100,13 @@ const inPackage = (
83
100
 
84
101
  // Check symbol name if specified
85
102
  if (symbolName && symbol.name !== symbolName) {
86
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
103
+ return ts.isIdentifier(node) && varnames.has(node.text);
87
104
  }
88
105
 
89
106
  let declarations = symbol.getDeclarations();
90
107
 
91
108
  if (!declarations || declarations.length === 0) {
92
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
109
+ return ts.isIdentifier(node) && varnames.has(node.text);
93
110
  }
94
111
 
95
112
  // Check if any declaration is from the expected package
@@ -99,9 +116,9 @@ const inPackage = (
99
116
  }
100
117
  }
101
118
 
102
- return packageImports ? ts.isIdentifier(node) && packageImports.has(node.text) : false;
119
+ return ts.isIdentifier(node) && varnames.has(node.text);
103
120
  };
104
121
 
105
122
 
106
- export default { find, inPackage };
123
+ export default { all, includes };
107
124
  export type { ImportInfo, ModifyOptions };
@@ -1,7 +1,7 @@
1
- export * as ast from './ast/index';
1
+ export { default as ast } from './ast';
2
2
  export { default as code } from './code';
3
3
  export { default as coordinator } from './coordinator';
4
4
  export { default as imports } from './imports';
5
- export { default as plugin } from './plugins/index';
5
+ export { default as plugin } from './plugins';
6
6
  export { default as uid } from './uid';
7
7
  export type * from './types';
@@ -1,24 +1,6 @@
1
- import type { Plugin, SharedContext } from '../types';
2
- import type ts from 'typescript';
3
- import coordinator from '../coordinator';
1
+ import type { Plugin } from '../types';
4
2
 
5
3
 
6
4
  export default (plugins: Plugin[]) => {
7
- return (program: ts.Program, shared: SharedContext) => {
8
- return {
9
- transform: (() => {
10
- return (sourceFile: ts.SourceFile) => {
11
- let result = coordinator.transform(
12
- plugins,
13
- sourceFile.getFullText(),
14
- sourceFile,
15
- program,
16
- shared
17
- );
18
-
19
- return result.changed ? result.sourceFile : sourceFile;
20
- };
21
- }) as ts.TransformerFactory<ts.SourceFile>
22
- };
23
- };
5
+ return () => plugins;
24
6
  };
@@ -1,5 +1,6 @@
1
1
  import path from 'path';
2
2
  import ts from 'typescript';
3
+ import { PACKAGE_NAME } from '~/constants';
3
4
 
4
5
 
5
6
  let cache = new Map<string, ts.Program>();
@@ -15,7 +16,7 @@ function create(root: string): ts.Program {
15
16
  let file = ts.readConfigFile(tsconfig, ts.sys.readFile);
16
17
 
17
18
  if (file.error) {
18
- throw new Error(`Error reading tsconfig.json: ${file.error.messageText}`);
19
+ throw new Error(`${PACKAGE_NAME}: error reading tsconfig.json ${file.error.messageText}`);
19
20
  }
20
21
 
21
22
  let parsed = ts.parseJsonConfigFileContent(
@@ -25,7 +26,7 @@ function create(root: string): ts.Program {
25
26
  );
26
27
 
27
28
  if (parsed.errors.length > 0) {
28
- throw new Error(`Error parsing tsconfig.json: ${parsed.errors[0].messageText}`);
29
+ throw new Error(`${PACKAGE_NAME}: error parsing tsconfig.json ${parsed.errors[0].messageText}`);
29
30
  }
30
31
 
31
32
  return ts.createProgram({
@@ -1,21 +1,10 @@
1
- const INVALID_CHARS = /[^A-Za-z0-9]/g;
1
+ import { uuid } from '@esportsplus/utilities';
2
2
 
3
3
 
4
- let counter = 0;
4
+ let i = 0,
5
+ namespace = uuid().replace(/[^A-Za-z0-9]/g, '');
5
6
 
6
7
 
7
- function hash(str: string): string {
8
- let h = 0x811c9dc5;
9
-
10
- for (let i = 0, n = str.length; i < n; i++) {
11
- h ^= str.charCodeAt(i);
12
- h = Math.imul(h, 0x01000193);
13
- }
14
-
15
- return ((h >>> 0).toString(36) + Math.abs(h).toString(36)).replace(INVALID_CHARS, '');
16
- }
17
-
18
-
19
- export default (prefix: string): string => {
20
- return prefix + '_' + hash(prefix) + (counter++).toString(36);
8
+ export default (name: string): string => {
9
+ return name + '_' + namespace + (i++).toString(36);
21
10
  };
@@ -0,0 +1,4 @@
1
+ const PACKAGE_NAME = '@esportsplus/typescript';
2
+
3
+
4
+ export { PACKAGE_NAME };
@@ -1,4 +0,0 @@
1
- import ts from 'typescript';
2
- declare const getExpressionName: (node: ts.Expression) => string | null;
3
- declare const getPropertyPath: (node: ts.Expression) => string | null;
4
- export { getExpressionName, getPropertyPath };
@@ -1,23 +0,0 @@
1
- import ts from 'typescript';
2
- const getExpressionName = (node) => {
3
- if (ts.isIdentifier(node)) {
4
- return node.text;
5
- }
6
- if (ts.isPropertyAccessExpression(node)) {
7
- return getPropertyPath(node);
8
- }
9
- return null;
10
- };
11
- const getPropertyPath = (node) => {
12
- let current = node, parts = [];
13
- while (ts.isPropertyAccessExpression(current)) {
14
- parts.push(current.name.text);
15
- current = current.expression;
16
- }
17
- if (ts.isIdentifier(current)) {
18
- parts.push(current.text);
19
- return parts.reverse().join('.');
20
- }
21
- return null;
22
- };
23
- export { getExpressionName, getPropertyPath };
@@ -1,3 +0,0 @@
1
- export * from './expression.js';
2
- export * from './range.js';
3
- export * from './visitor.js';
@@ -1,3 +0,0 @@
1
- export * from './expression.js';
2
- export * from './range.js';
3
- export * from './visitor.js';
@@ -1,7 +0,0 @@
1
- type Range = {
2
- end: number;
3
- start: number;
4
- };
5
- declare const inRange: (ranges: Range[], start: number, end: number) => boolean;
6
- export { inRange };
7
- export type { Range };
@@ -1,10 +0,0 @@
1
- const inRange = (ranges, start, end) => {
2
- for (let i = 0, n = ranges.length; i < n; i++) {
3
- let r = ranges[i];
4
- if (start >= r.start && end <= r.end) {
5
- return true;
6
- }
7
- }
8
- return false;
9
- };
10
- export { inRange };
@@ -1,3 +0,0 @@
1
- import ts from 'typescript';
2
- declare const hasMatch: (node: ts.Node, predicate: (n: ts.Node) => boolean) => boolean;
3
- export { hasMatch };
@@ -1,8 +0,0 @@
1
- import ts from 'typescript';
2
- const hasMatch = (node, predicate) => {
3
- if (predicate(node)) {
4
- return true;
5
- }
6
- return !!ts.forEachChild(node, child => hasMatch(child, predicate) || undefined);
7
- };
8
- export { hasMatch };
@@ -1,34 +0,0 @@
1
- import ts from 'typescript';
2
-
3
-
4
- const getExpressionName = (node: ts.Expression): string | null => {
5
- if (ts.isIdentifier(node)) {
6
- return node.text;
7
- }
8
-
9
- if (ts.isPropertyAccessExpression(node)) {
10
- return getPropertyPath(node);
11
- }
12
-
13
- return null;
14
- };
15
-
16
- const getPropertyPath = (node: ts.Expression): string | null => {
17
- let current: ts.Node = node,
18
- parts: string[] = [];
19
-
20
- while (ts.isPropertyAccessExpression(current)) {
21
- parts.push(current.name.text);
22
- current = current.expression;
23
- }
24
-
25
- if (ts.isIdentifier(current)) {
26
- parts.push(current.text);
27
- return parts.reverse().join('.');
28
- }
29
-
30
- return null;
31
- };
32
-
33
-
34
- export { getExpressionName, getPropertyPath };
@@ -1,3 +0,0 @@
1
- export * from './expression';
2
- export * from './range';
3
- export * from './visitor';
@@ -1,21 +0,0 @@
1
- type Range = {
2
- end: number;
3
- start: number;
4
- };
5
-
6
-
7
- const inRange = (ranges: Range[], start: number, end: number): boolean => {
8
- for (let i = 0, n = ranges.length; i < n; i++) {
9
- let r = ranges[i];
10
-
11
- if (start >= r.start && end <= r.end) {
12
- return true;
13
- }
14
- }
15
-
16
- return false;
17
- };
18
-
19
-
20
- export { inRange };
21
- export type { Range };
@@ -1,13 +0,0 @@
1
- import ts from 'typescript';
2
-
3
-
4
- const hasMatch = (node: ts.Node, predicate: (n: ts.Node) => boolean): boolean => {
5
- if (predicate(node)) {
6
- return true;
7
- }
8
-
9
- return !!ts.forEachChild(node, child => hasMatch(child, predicate) || undefined);
10
- };
11
-
12
-
13
- export { hasMatch };