@knighted/module 1.0.0-alpha.9 → 1.0.0-beta.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 (85) hide show
  1. package/README.md +28 -16
  2. package/dist/assignmentExpression.d.ts +12 -0
  3. package/dist/cjs/assignmentExpression.d.cts +12 -0
  4. package/dist/cjs/exports.d.cts +6 -0
  5. package/dist/cjs/expressionStatement.d.cts +2 -3
  6. package/dist/cjs/format.cjs +133 -26
  7. package/dist/cjs/format.d.cts +6 -6
  8. package/dist/cjs/formatters/assignmentExpression.cjs +37 -0
  9. package/dist/cjs/formatters/expressionStatement.cjs +36 -55
  10. package/dist/cjs/formatters/identifier.cjs +31 -31
  11. package/dist/cjs/formatters/memberExpression.cjs +24 -11
  12. package/dist/cjs/formatters/metaProperty.cjs +23 -35
  13. package/dist/cjs/helpers/identifier.cjs +132 -0
  14. package/dist/cjs/helpers/scope.cjs +12 -0
  15. package/dist/cjs/identifier.d.cts +31 -5
  16. package/dist/cjs/identifiers.d.cts +19 -0
  17. package/dist/cjs/lang.d.cts +4 -0
  18. package/dist/cjs/memberExpression.d.cts +2 -3
  19. package/dist/cjs/metaProperty.d.cts +2 -3
  20. package/dist/cjs/module.cjs +26 -27
  21. package/dist/cjs/parse.cjs +3 -14
  22. package/dist/cjs/parse.d.cts +1 -1
  23. package/dist/cjs/scope.d.cts +6 -0
  24. package/dist/cjs/types.d.cts +40 -4
  25. package/dist/cjs/url.d.cts +2 -0
  26. package/dist/cjs/utils/exports.cjs +227 -0
  27. package/dist/cjs/utils/identifiers.cjs +190 -0
  28. package/dist/cjs/utils/lang.cjs +25 -0
  29. package/dist/cjs/utils/url.cjs +16 -0
  30. package/dist/cjs/utils.cjs +288 -0
  31. package/dist/cjs/utils.d.cts +26 -0
  32. package/dist/cjs/walk.cjs +75 -0
  33. package/dist/cjs/walk.d.cts +20 -0
  34. package/dist/exports.d.ts +6 -0
  35. package/dist/expressionStatement.d.ts +2 -3
  36. package/dist/format.d.ts +6 -6
  37. package/dist/format.js +135 -27
  38. package/dist/formatters/assignmentExpression.js +30 -0
  39. package/dist/formatters/expressionStatement.js +36 -55
  40. package/dist/formatters/identifier.js +31 -31
  41. package/dist/formatters/memberExpression.js +24 -11
  42. package/dist/formatters/metaProperty.js +23 -35
  43. package/dist/helpers/identifier.js +127 -0
  44. package/dist/helpers/scope.js +7 -0
  45. package/dist/identifier.d.ts +31 -5
  46. package/dist/identifiers.d.ts +19 -0
  47. package/dist/lang.d.ts +4 -0
  48. package/dist/memberExpression.d.ts +2 -3
  49. package/dist/metaProperty.d.ts +2 -3
  50. package/dist/module.js +26 -27
  51. package/dist/parse.d.ts +1 -1
  52. package/dist/parse.js +3 -14
  53. package/dist/scope.d.ts +6 -0
  54. package/dist/src/format.d.ts +9 -0
  55. package/dist/src/formatters/assignmentExpression.d.ts +12 -0
  56. package/dist/src/formatters/expressionStatement.d.ts +4 -0
  57. package/dist/src/formatters/identifier.d.ts +12 -0
  58. package/dist/src/formatters/memberExpression.d.ts +4 -0
  59. package/dist/src/formatters/metaProperty.d.ts +4 -0
  60. package/dist/src/helpers/identifier.d.ts +31 -0
  61. package/dist/src/helpers/scope.d.ts +6 -0
  62. package/dist/src/module.d.ts +3 -0
  63. package/dist/src/parse.d.ts +2 -0
  64. package/dist/src/types.d.ts +43 -0
  65. package/dist/src/utils/exports.d.ts +6 -0
  66. package/dist/src/utils/identifiers.d.ts +19 -0
  67. package/dist/src/utils/lang.d.ts +4 -0
  68. package/dist/src/utils/url.d.ts +2 -0
  69. package/dist/src/utils.d.ts +26 -0
  70. package/dist/src/walk.d.ts +20 -0
  71. package/dist/types.d.ts +40 -4
  72. package/dist/url.d.ts +2 -0
  73. package/dist/utils/exports.js +221 -0
  74. package/dist/utils/identifiers.js +183 -0
  75. package/dist/utils/lang.js +20 -0
  76. package/dist/utils/url.js +10 -0
  77. package/dist/utils.d.ts +26 -0
  78. package/dist/utils.js +278 -0
  79. package/dist/walk.d.ts +20 -0
  80. package/dist/walk.js +69 -0
  81. package/package.json +43 -25
  82. package/dist/formatters/expressionStatement.d.ts +0 -5
  83. package/dist/formatters/identifier.d.ts +0 -5
  84. package/dist/formatters/memberExpression.d.ts +0 -5
  85. package/dist/formatters/metaProperty.d.ts +0 -5
@@ -0,0 +1,221 @@
1
+ import { ancestorWalk } from '#walk';
2
+ const exportsRename = '__exports';
3
+ const requireMainRgx = /(require\.main\s*===\s*module|module\s*===\s*require\.main)/g;
4
+ const literalPropName = (prop, literals) => {
5
+ if (prop.type === 'Identifier') return literals?.get(prop.name)?.toString() ?? prop.name;
6
+ if (prop.type === 'Literal' && (typeof prop.value === 'string' || typeof prop.value === 'number')) {
7
+ return String(prop.value);
8
+ }
9
+ if (prop.type === 'TemplateLiteral' && prop.expressions.length === 0 && prop.quasis.length === 1) {
10
+ return prop.quasis[0].value.cooked ?? prop.quasis[0].value.raw;
11
+ }
12
+ return null;
13
+ };
14
+ const resolveExportTarget = (node, aliases, literals) => {
15
+ if (node.type === 'Identifier' && node.name === 'exports') {
16
+ return {
17
+ key: 'default',
18
+ via: 'exports'
19
+ };
20
+ }
21
+ if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'module' && node.property.type === 'Identifier' && node.property.name === 'exports') {
22
+ return {
23
+ key: 'default',
24
+ via: 'module.exports'
25
+ };
26
+ }
27
+ if (node.type !== 'MemberExpression') return null;
28
+ const base = node.object;
29
+ const prop = node.property;
30
+ const key = literalPropName(prop, literals);
31
+ if (!key) return null;
32
+ const baseVia = resolveBase(base, aliases);
33
+ if (!baseVia) return null;
34
+ if (baseVia === 'module.exports' && key === 'exports') {
35
+ return {
36
+ key: 'default',
37
+ via: 'module.exports'
38
+ };
39
+ }
40
+ return {
41
+ key,
42
+ via: baseVia
43
+ };
44
+ };
45
+ const resolveBase = (node, aliases) => {
46
+ if (node.type === 'Identifier') {
47
+ if (node.name === 'exports') return 'exports';
48
+ const alias = aliases.get(node.name);
49
+ if (alias) return alias;
50
+ }
51
+ if (node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.object.name === 'module' && node.property.type === 'Identifier' && node.property.name === 'exports') {
52
+ return 'module.exports';
53
+ }
54
+ return null;
55
+ };
56
+ const collectCjsExports = async ast => {
57
+ const exportsMap = new Map();
58
+ const localToExport = new Map();
59
+ const aliases = new Map();
60
+ const literals = new Map();
61
+ const addExport = (ref, node, rhs, options) => {
62
+ const entry = exportsMap.get(ref.key) ?? {
63
+ key: ref.key,
64
+ writes: [],
65
+ via: new Set(),
66
+ reassignments: []
67
+ };
68
+ entry.via.add(ref.via);
69
+ entry.writes.push(node);
70
+ if (options?.hasGetter) {
71
+ entry.hasGetter = true;
72
+ }
73
+ if (rhs) {
74
+ entry.fromIdentifier ??= rhs.name;
75
+ const set = localToExport.get(rhs.name) ?? new Set();
76
+ set.add(ref.key);
77
+ localToExport.set(rhs.name, set);
78
+ }
79
+ exportsMap.set(ref.key, entry);
80
+ };
81
+ await ancestorWalk(ast, {
82
+ enter(node) {
83
+ if (node.type === 'VariableDeclarator' && node.id.type === 'Identifier' && node.init) {
84
+ const via = resolveBase(node.init, aliases);
85
+ if (via) {
86
+ aliases.set(node.id.name, via);
87
+ }
88
+ if (node.init.type === 'Literal' && (typeof node.init.value === 'string' || typeof node.init.value === 'number')) {
89
+ literals.set(node.id.name, node.init.value);
90
+ }
91
+ if (node.init.type === 'TemplateLiteral' && node.init.expressions.length === 0 && node.init.quasis.length === 1) {
92
+ const cooked = node.init.quasis[0].value.cooked ?? node.init.quasis[0].value.raw;
93
+ literals.set(node.id.name, cooked);
94
+ }
95
+ }
96
+ if (node.type === 'AssignmentExpression') {
97
+ const target = resolveExportTarget(node.left, aliases, literals);
98
+ if (target) {
99
+ const rhsIdent = node.right.type === 'Identifier' ? node.right : undefined;
100
+ addExport(target, node, rhsIdent);
101
+ return;
102
+ }
103
+ if (node.left.type === 'Identifier') {
104
+ const keys = localToExport.get(node.left.name);
105
+ if (keys) {
106
+ keys.forEach(key => {
107
+ const entry = exportsMap.get(key);
108
+ if (entry) {
109
+ entry.reassignments.push(node);
110
+ exportsMap.set(key, entry);
111
+ }
112
+ });
113
+ }
114
+ }
115
+ if (node.left.type === 'ObjectPattern') {
116
+ for (const prop of node.left.properties) {
117
+ if (prop.type === 'Property' && prop.value.type === 'MemberExpression') {
118
+ const ref = resolveExportTarget(prop.value, aliases, literals);
119
+ if (ref) {
120
+ addExport(ref, node);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ if (node.type === 'CallExpression') {
127
+ const callee = node.callee;
128
+
129
+ // Object.assign(exports, { foo: bar })
130
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'assign' && node.arguments.length >= 2) {
131
+ const targetArg = node.arguments[0];
132
+ const ref = resolveBase(targetArg, aliases);
133
+ if (!ref) return;
134
+ for (let i = 1; i < node.arguments.length; i++) {
135
+ const arg = node.arguments[i];
136
+ if (arg.type === 'ObjectExpression') {
137
+ for (const prop of arg.properties) {
138
+ if (prop.type !== 'Property') continue;
139
+ const keyName = literalPropName(prop.key, literals);
140
+ if (!keyName) continue;
141
+ let rhsIdent;
142
+ if (prop.value.type === 'Identifier') {
143
+ rhsIdent = prop.value;
144
+ }
145
+ addExport({
146
+ key: keyName,
147
+ via: ref
148
+ }, node, rhsIdent);
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Object.defineProperty(exports, 'foo', { value, get, set })
155
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperty' && node.arguments.length >= 3) {
156
+ const target = resolveBase(node.arguments[0], aliases);
157
+ if (!target) return;
158
+ const keyName = literalPropName(node.arguments[1], literals);
159
+ if (!keyName) return;
160
+ const desc = node.arguments[2];
161
+ if (desc.type !== 'ObjectExpression') return;
162
+ let rhsIdent;
163
+ let hasGetter = false;
164
+ for (const prop of desc.properties) {
165
+ if (prop.type !== 'Property') continue;
166
+ if (prop.key.type !== 'Identifier') continue;
167
+ if (prop.key.name === 'value' && prop.value.type === 'Identifier') {
168
+ rhsIdent = prop.value;
169
+ }
170
+ if (prop.key.name === 'get' && prop.value.type === 'Identifier') {
171
+ hasGetter = true;
172
+ }
173
+ if (prop.key.name === 'set' && prop.value.type === 'Identifier') {
174
+ // Setter-only doesn’t create a readable export; ignore beyond marking write
175
+ }
176
+ }
177
+ addExport({
178
+ key: keyName,
179
+ via: target
180
+ }, node, rhsIdent, {
181
+ hasGetter
182
+ });
183
+ }
184
+
185
+ // Object.defineProperties(exports, { foo: { value: ... }, bar: { get: ... } })
186
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.object.name === 'Object' && callee.property.type === 'Identifier' && callee.property.name === 'defineProperties' && node.arguments.length >= 2) {
187
+ const target = resolveBase(node.arguments[0], aliases);
188
+ if (!target) return;
189
+ const descMap = node.arguments[1];
190
+ if (descMap.type !== 'ObjectExpression') return;
191
+ for (const prop of descMap.properties) {
192
+ if (prop.type !== 'Property') continue;
193
+ const keyName = literalPropName(prop.key, literals);
194
+ if (!keyName) continue;
195
+ if (prop.value.type !== 'ObjectExpression') continue;
196
+ let rhsIdent;
197
+ let hasGetter = false;
198
+ for (const descProp of prop.value.properties) {
199
+ if (descProp.type !== 'Property') continue;
200
+ if (descProp.key.type !== 'Identifier') continue;
201
+ if (descProp.key.name === 'value' && descProp.value.type === 'Identifier') {
202
+ rhsIdent = descProp.value;
203
+ }
204
+ if (descProp.key.name === 'get' && descProp.value.type === 'Identifier') {
205
+ hasGetter = true;
206
+ }
207
+ }
208
+ addExport({
209
+ key: keyName,
210
+ via: target
211
+ }, node, rhsIdent, {
212
+ hasGetter
213
+ });
214
+ }
215
+ }
216
+ }
217
+ }
218
+ });
219
+ return exportsMap;
220
+ };
221
+ export { exportsRename, requireMainRgx, collectCjsExports };
@@ -0,0 +1,183 @@
1
+ import { ancestorWalk } from '#walk';
2
+ import { scopes as scopeNodes } from '#helpers/scope.js';
3
+ import { identifier } from '#helpers/identifier.js';
4
+ const collectScopeIdentifiers = (node, scopes) => {
5
+ const {
6
+ type
7
+ } = node;
8
+ switch (type) {
9
+ case 'BlockStatement':
10
+ case 'ClassBody':
11
+ scopes.push({
12
+ node,
13
+ type: 'Block',
14
+ name: type,
15
+ idents: new Set()
16
+ });
17
+ break;
18
+ case 'FunctionDeclaration':
19
+ case 'FunctionExpression':
20
+ case 'ArrowFunctionExpression':
21
+ {
22
+ const name = node.id ? node.id.name : 'anonymous';
23
+ const scope = {
24
+ node,
25
+ name,
26
+ type: 'Function',
27
+ idents: new Set()
28
+ };
29
+ node.params.map(param => {
30
+ if (param.type === 'TSParameterProperty') {
31
+ return param.parameter;
32
+ }
33
+ if (param.type === 'RestElement') {
34
+ return param.argument;
35
+ }
36
+ if (param.type === 'AssignmentPattern') {
37
+ return param.left;
38
+ }
39
+ return param;
40
+ }).filter(identifier.isNamed).forEach(param => {
41
+ scope.idents.add(param.name);
42
+ });
43
+
44
+ /**
45
+ * If a FunctionExpression has an id, it is a named function expression.
46
+ * The function expression name shadows the module scope identifier, so
47
+ * we don't want to count reads of module identifers that have the same name.
48
+ * They also do not cause a SyntaxError if the function expression name is
49
+ * the same as a module scope identifier.
50
+ *
51
+ * TODO: Is this necessary for FunctionDeclaration?
52
+ */
53
+ if (node.type === 'FunctionExpression' && node.id) {
54
+ scope.idents.add(node.id.name);
55
+ }
56
+
57
+ // First add the function to any previous scopes
58
+ if (scopes.length > 0) {
59
+ scopes[scopes.length - 1].idents.add(name);
60
+ }
61
+
62
+ // Then add the function scope to the scopes stack
63
+ scopes.push(scope);
64
+ }
65
+ break;
66
+ case 'ClassDeclaration':
67
+ {
68
+ const className = node.id ? node.id.name : 'anonymous';
69
+
70
+ // First add the class to any previous scopes
71
+ if (scopes.length > 0) {
72
+ scopes[scopes.length - 1].idents.add(className);
73
+ }
74
+
75
+ // Then add the class to the scopes stack
76
+ scopes.push({
77
+ node,
78
+ name: className,
79
+ type: 'Class',
80
+ idents: new Set()
81
+ });
82
+ }
83
+ break;
84
+ case 'ClassExpression':
85
+ {}
86
+ break;
87
+ case 'VariableDeclaration':
88
+ if (scopes.length > 0) {
89
+ const scope = scopes[scopes.length - 1];
90
+ node.declarations.forEach(decl => {
91
+ if (decl.type === 'VariableDeclarator' && decl.id.type === 'Identifier') {
92
+ scope.idents.add(decl.id.name);
93
+ }
94
+ });
95
+ }
96
+ break;
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Collects all module scope identifiers in the AST.
102
+ *
103
+ * Ignores identifiers that are in functions or classes.
104
+ * Ignores new scopes for StaticBlock nodes (can only reference static class members).
105
+ *
106
+ * Special case handling for these which create their own scopes,
107
+ * but are also valid module scope identifiers:
108
+ * - ClassDeclaration
109
+ * - FunctionDeclaration
110
+ *
111
+ * Special case handling for var inside BlockStatement
112
+ * which are also valid module scope identifiers.
113
+ */
114
+ const collectModuleIdentifiers = async (ast, hoisting = true) => {
115
+ const identifiers = new Map();
116
+ const globalReads = new Map();
117
+ const scopes = [];
118
+ await ancestorWalk(ast, {
119
+ enter(node, ancestors) {
120
+ const {
121
+ type
122
+ } = node;
123
+ collectScopeIdentifiers(node, scopes);
124
+
125
+ // Add module scope identifiers to the registry map
126
+
127
+ if (type === 'Identifier') {
128
+ const {
129
+ name
130
+ } = node;
131
+ const meta = identifiers.get(name) ?? {
132
+ declare: [],
133
+ read: []
134
+ };
135
+ const isDeclaration = identifier.isDeclaration(ancestors);
136
+ const inScope = scopes.some(scope => scope.idents.has(name) || scope.name === name);
137
+ if (hoisting && !identifier.isDeclaration(ancestors) && !identifier.isFunctionExpressionId(ancestors) && !identifier.isExportSpecifierAlias(ancestors) && !identifier.isClassPropertyKey(ancestors) && !identifier.isMethodDefinitionKey(ancestors) && !identifier.isMemberKey(ancestors) && !identifier.isPropertyKey(ancestors) && !identifier.isIife(ancestors) && !inScope) {
138
+ if (globalReads.has(name)) {
139
+ globalReads.get(name)?.push(node);
140
+ } else {
141
+ globalReads.set(name, [node]);
142
+ }
143
+ }
144
+ if (isDeclaration) {
145
+ const isModuleScope = identifier.isModuleScope(ancestors);
146
+ const isClassOrFuncDeclaration = identifier.isClassOrFuncDeclarationId(ancestors);
147
+ const isVarDeclarationInGlobalScope = identifier.isVarDeclarationInGlobalScope(ancestors);
148
+ if (isModuleScope || isClassOrFuncDeclaration || isVarDeclarationInGlobalScope) {
149
+ meta.declare.push(node);
150
+
151
+ // Check for hoisted reads
152
+ if (hoisting && globalReads.has(name)) {
153
+ const reads = globalReads.get(name);
154
+ if (reads) {
155
+ reads.forEach(read => {
156
+ if (!meta.read.includes(read)) {
157
+ meta.read.push(read);
158
+ }
159
+ });
160
+ }
161
+ }
162
+ identifiers.set(name, meta);
163
+ }
164
+ } else {
165
+ if (identifiers.has(name) && !inScope && !identifier.isIife(ancestors) && !identifier.isFunctionExpressionId(ancestors) && !identifier.isExportSpecifierAlias(ancestors) && !identifier.isClassPropertyKey(ancestors) && !identifier.isMethodDefinitionKey(ancestors) && !identifier.isMemberKey(ancestors) && !identifier.isPropertyKey(ancestors)) {
166
+ // Closure is referencing module scope identifier
167
+ meta.read.push(node);
168
+ }
169
+ }
170
+ }
171
+ },
172
+ leave(node) {
173
+ const {
174
+ type
175
+ } = node;
176
+ if (scopeNodes.includes(type)) {
177
+ scopes.pop();
178
+ }
179
+ }
180
+ });
181
+ return identifiers;
182
+ };
183
+ export { collectScopeIdentifiers, collectModuleIdentifiers };
@@ -0,0 +1,20 @@
1
+ import { extname } from 'node:path';
2
+
3
+ // Determine language from filename extension for specifier rewrite.
4
+
5
+ const getLangFromExt = filename => {
6
+ const ext = extname(filename);
7
+ if (ext.endsWith('.js')) {
8
+ return 'js';
9
+ }
10
+ if (ext.endsWith('.ts')) {
11
+ return 'ts';
12
+ }
13
+ if (ext === '.tsx') {
14
+ return 'tsx';
15
+ }
16
+ if (ext === '.jsx') {
17
+ return 'jsx';
18
+ }
19
+ };
20
+ export { getLangFromExt };
@@ -0,0 +1,10 @@
1
+ // URL validation used when rewriting import.meta.url assignments.
2
+ const isValidUrl = url => {
3
+ try {
4
+ new URL(url);
5
+ return true;
6
+ } catch {
7
+ return false;
8
+ }
9
+ };
10
+ export { isValidUrl };
@@ -0,0 +1,26 @@
1
+ import type { Node } from 'oxc-parser';
2
+ import type { Specifier } from '@knighted/specifier';
3
+ import type { IdentMeta, Scope, CjsExport } from './types.js';
4
+ type UpdateSrcLang = Parameters<Specifier['updateSrc']>[1];
5
+ declare const getLangFromExt: (filename: string) => UpdateSrcLang;
6
+ declare const isValidUrl: (url: string) => boolean;
7
+ declare const exportsRename = "__exports";
8
+ declare const requireMainRgx: RegExp;
9
+ declare const collectCjsExports: (ast: Node) => Promise<Map<string, CjsExport>>;
10
+ declare const collectScopeIdentifiers: (node: Node, scopes: Scope[]) => void;
11
+ /**
12
+ * Collects all module scope identifiers in the AST.
13
+ *
14
+ * Ignores identifiers that are in functions or classes.
15
+ * Ignores new scopes for StaticBlock nodes (can only reference static class members).
16
+ *
17
+ * Special case handling for these which create their own scopes,
18
+ * but are also valid module scope identifiers:
19
+ * - ClassDeclaration
20
+ * - FunctionDeclaration
21
+ *
22
+ * Special case handling for var inside BlockStatement
23
+ * which are also valid module scope identifiers.
24
+ */
25
+ declare const collectModuleIdentifiers: (ast: Node, hoisting?: boolean) => Promise<Map<string, IdentMeta>>;
26
+ export { getLangFromExt, isValidUrl, collectScopeIdentifiers, collectModuleIdentifiers, collectCjsExports, exportsRename, requireMainRgx, };