wrec 0.40.3 → 0.42.0
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.
- package/README.md +9 -15
- package/dist/{wrec-DHGadgxK.js → wrec-RkviIlbF.js} +17 -8
- package/dist/wrec-ssr.d.ts +1 -0
- package/dist/wrec-ssr.es.js +3 -3
- package/dist/wrec.d.ts +1 -0
- package/dist/wrec.es.js +1 -1
- package/package.json +39 -36
- package/scripts/ast-utils.js +20 -28
- package/scripts/declare.js +41 -62
- package/scripts/lint.js +609 -927
- package/scripts/scaffold.js +11 -13
- package/scripts/used-by.js +76 -119
package/scripts/scaffold.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
import {fileURLToPath} from
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
function fail(message) {
|
|
8
8
|
console.error(message);
|
|
@@ -11,30 +11,28 @@ function fail(message) {
|
|
|
11
11
|
|
|
12
12
|
function toClassName(tagName) {
|
|
13
13
|
return tagName
|
|
14
|
-
.split(
|
|
14
|
+
.split("-")
|
|
15
15
|
.filter(Boolean)
|
|
16
|
-
.map(part => part[0].toUpperCase() + part.slice(1))
|
|
17
|
-
.join(
|
|
16
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
17
|
+
.join("");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
const [tagName] = process.argv.slice(2);
|
|
21
21
|
|
|
22
|
-
if (!tagName) fail(
|
|
23
|
-
if (!tagName.includes(
|
|
22
|
+
if (!tagName) fail("usage: npx wrec-scaffold {tag-name}");
|
|
23
|
+
if (!tagName.includes("-")) fail("tag name must contain at least one hyphen");
|
|
24
24
|
|
|
25
25
|
const className = toClassName(tagName);
|
|
26
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
27
|
const __dirname = path.dirname(__filename);
|
|
28
|
-
const templatePath = path.join(__dirname,
|
|
28
|
+
const templatePath = path.join(__dirname, "template.ts");
|
|
29
29
|
const outputPath = path.resolve(process.cwd(), `${tagName}.ts`);
|
|
30
30
|
|
|
31
31
|
if (fs.existsSync(outputPath)) {
|
|
32
32
|
fail(`file already exists: ${outputPath}`);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const template = fs.readFileSync(templatePath,
|
|
36
|
-
const output = template
|
|
37
|
-
.replaceAll('{class}', className)
|
|
38
|
-
.replaceAll('{tag}', tagName);
|
|
35
|
+
const template = fs.readFileSync(templatePath, "utf8");
|
|
36
|
+
const output = template.replaceAll("{class}", className).replaceAll("{tag}", tagName);
|
|
39
37
|
|
|
40
38
|
fs.writeFileSync(outputPath, output);
|
package/scripts/used-by.js
CHANGED
|
@@ -16,22 +16,21 @@
|
|
|
16
16
|
// Include the --dry flag for a dry run where `usedBy` values are output,
|
|
17
17
|
// but no files are modified.
|
|
18
18
|
|
|
19
|
-
import fs from
|
|
20
|
-
import path from
|
|
21
|
-
import ts from
|
|
19
|
+
import fs from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import ts from "typescript";
|
|
22
22
|
import {
|
|
23
23
|
extendsWrec,
|
|
24
24
|
getNameText,
|
|
25
25
|
getPropertyAssignmentNames,
|
|
26
26
|
getWrecImportInfo,
|
|
27
|
-
hasStaticModifier
|
|
28
|
-
} from
|
|
27
|
+
hasStaticModifier,
|
|
28
|
+
} from "./ast-utils.js";
|
|
29
29
|
|
|
30
30
|
const cwd = process.cwd();
|
|
31
31
|
const CALL_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/g;
|
|
32
|
-
const REFS_RE =
|
|
33
|
-
|
|
34
|
-
const GETTER_PREFIX = 'get ';
|
|
32
|
+
const REFS_RE = /this\.([A-Za-z_$][A-Za-z0-9_$]*)(\.[A-Za-z_$][A-Za-z0-9_$]*)*/g;
|
|
33
|
+
const GETTER_PREFIX = "get ";
|
|
35
34
|
|
|
36
35
|
// Records property names introduced by
|
|
37
36
|
// an identifier or object binding pattern.
|
|
@@ -65,7 +64,7 @@ function addObjectBindingProps(props, bindingPattern) {
|
|
|
65
64
|
// This is the heart of the functionality in this script.
|
|
66
65
|
function analyzeSourceFile(sourceFile) {
|
|
67
66
|
const edits = [];
|
|
68
|
-
const {names: wrecNames, quote} = getWrecImportInfo(sourceFile);
|
|
67
|
+
const { names: wrecNames, quote } = getWrecImportInfo(sourceFile);
|
|
69
68
|
const suggestions = [];
|
|
70
69
|
let foundWrecSubclass = false;
|
|
71
70
|
|
|
@@ -80,7 +79,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
80
79
|
if (
|
|
81
80
|
ts.isPropertyDeclaration(member) &&
|
|
82
81
|
hasStaticModifier(member) &&
|
|
83
|
-
getNameText(member.name) ===
|
|
82
|
+
getNameText(member.name) === "properties" &&
|
|
84
83
|
member.initializer &&
|
|
85
84
|
ts.isObjectLiteralExpression(member.initializer)
|
|
86
85
|
) {
|
|
@@ -107,37 +106,30 @@ function analyzeSourceFile(sourceFile) {
|
|
|
107
106
|
// Skip the member if we can't gets its name
|
|
108
107
|
// or if its value isn't an object literal.
|
|
109
108
|
const propName = getNameText(member.name);
|
|
110
|
-
if (!propName || !ts.isObjectLiteralExpression(member.initializer))
|
|
111
|
-
continue;
|
|
109
|
+
if (!propName || !ts.isObjectLiteralExpression(member.initializer)) continue;
|
|
112
110
|
|
|
113
111
|
// Convert the Set of methods that use the property into a sorted array.
|
|
114
|
-
const methodNames = [
|
|
115
|
-
...(propToMethods.get(propName) ?? new Set())
|
|
116
|
-
].sort();
|
|
112
|
+
const methodNames = [...(propToMethods.get(propName) ?? new Set())].sort();
|
|
117
113
|
|
|
118
114
|
// Get an array of all the NodeObjects that represent
|
|
119
115
|
// properties in the configuration object, except the one for "usedBy".
|
|
120
116
|
const configObject = member.initializer;
|
|
121
117
|
const existingMembers = configObject.properties.filter(
|
|
122
|
-
property =>
|
|
123
|
-
!(
|
|
124
|
-
ts.isPropertyAssignment(property) &&
|
|
125
|
-
getNameText(property.name) === 'usedBy'
|
|
126
|
-
)
|
|
118
|
+
(property) =>
|
|
119
|
+
!(ts.isPropertyAssignment(property) && getNameText(property.name) === "usedBy"),
|
|
127
120
|
);
|
|
128
121
|
|
|
129
122
|
// If the property is used by any methods ...
|
|
130
123
|
if (methodNames.length > 0) {
|
|
131
124
|
suggestions.push({
|
|
132
125
|
propName,
|
|
133
|
-
suggestion: createUsedByProperty(methodNames, quote)
|
|
126
|
+
suggestion: createUsedByProperty(methodNames, quote),
|
|
134
127
|
});
|
|
135
128
|
} else {
|
|
136
129
|
// Determine if the configuration object already had a "usedBy" property.
|
|
137
|
-
const hadUsedBy =
|
|
138
|
-
existingMembers.length !== configObject.properties.length;
|
|
130
|
+
const hadUsedBy = existingMembers.length !== configObject.properties.length;
|
|
139
131
|
if (hadUsedBy) {
|
|
140
|
-
suggestions.push({propName, suggestion:
|
|
132
|
+
suggestions.push({ propName, suggestion: "remove usedBy" });
|
|
141
133
|
} else {
|
|
142
134
|
continue;
|
|
143
135
|
}
|
|
@@ -148,7 +140,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
148
140
|
// Get the existing text for the property configuration object.
|
|
149
141
|
const currentText = sourceFile.text.slice(
|
|
150
142
|
configObject.getStart(sourceFile),
|
|
151
|
-
configObject.end
|
|
143
|
+
configObject.end,
|
|
152
144
|
);
|
|
153
145
|
// If they differ, add an object describing
|
|
154
146
|
// the desired edit to the edits array.
|
|
@@ -158,7 +150,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
158
150
|
end: configObject.end,
|
|
159
151
|
propName,
|
|
160
152
|
oldText: currentText,
|
|
161
|
-
text: nextText
|
|
153
|
+
text: nextText,
|
|
162
154
|
});
|
|
163
155
|
}
|
|
164
156
|
}
|
|
@@ -171,7 +163,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
171
163
|
edits: [],
|
|
172
164
|
foundWrecSubclass: false,
|
|
173
165
|
suggestions,
|
|
174
|
-
text: sourceFile.text
|
|
166
|
+
text: sourceFile.text,
|
|
175
167
|
};
|
|
176
168
|
}
|
|
177
169
|
|
|
@@ -183,7 +175,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
183
175
|
edits: [],
|
|
184
176
|
foundWrecSubclass: true,
|
|
185
177
|
suggestions,
|
|
186
|
-
text: sourceFile.text
|
|
178
|
+
text: sourceFile.text,
|
|
187
179
|
};
|
|
188
180
|
}
|
|
189
181
|
|
|
@@ -195,8 +187,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
195
187
|
|
|
196
188
|
// Make each of the edits to the source file.
|
|
197
189
|
for (const edit of edits) {
|
|
198
|
-
nextSource =
|
|
199
|
-
nextSource.slice(0, edit.start) + edit.text + nextSource.slice(edit.end);
|
|
190
|
+
nextSource = nextSource.slice(0, edit.start) + edit.text + nextSource.slice(edit.end);
|
|
200
191
|
}
|
|
201
192
|
|
|
202
193
|
return {
|
|
@@ -204,7 +195,7 @@ function analyzeSourceFile(sourceFile) {
|
|
|
204
195
|
edits,
|
|
205
196
|
foundWrecSubclass: true,
|
|
206
197
|
suggestions,
|
|
207
|
-
text: nextSource
|
|
198
|
+
text: nextSource,
|
|
208
199
|
};
|
|
209
200
|
}
|
|
210
201
|
|
|
@@ -220,17 +211,13 @@ function buildConfigText(sourceFile, member, methodNames, quote) {
|
|
|
220
211
|
// Get an array of AST nodes for all the config object properties
|
|
221
212
|
// except `usedBy`.
|
|
222
213
|
const existingMembers = configObject.properties.filter(
|
|
223
|
-
property =>
|
|
224
|
-
!(
|
|
225
|
-
ts.isPropertyAssignment(property) &&
|
|
226
|
-
getNameText(property.name) === 'usedBy'
|
|
227
|
-
)
|
|
214
|
+
(property) => !(ts.isPropertyAssignment(property) && getNameText(property.name) === "usedBy"),
|
|
228
215
|
);
|
|
229
216
|
|
|
230
217
|
// Create property assignment strings from the AST nodes.
|
|
231
|
-
const {text} = sourceFile;
|
|
232
|
-
const propertyStrings = existingMembers.map(property =>
|
|
233
|
-
text.slice(property.getStart(sourceFile), property.end).trim()
|
|
218
|
+
const { text } = sourceFile;
|
|
219
|
+
const propertyStrings = existingMembers.map((property) =>
|
|
220
|
+
text.slice(property.getStart(sourceFile), property.end).trim(),
|
|
234
221
|
);
|
|
235
222
|
|
|
236
223
|
// If this property is used by any methods,
|
|
@@ -239,12 +226,9 @@ function buildConfigText(sourceFile, member, methodNames, quote) {
|
|
|
239
226
|
propertyStrings.push(createUsedByProperty(methodNames, quote));
|
|
240
227
|
}
|
|
241
228
|
|
|
242
|
-
const existingPropertiesString = text.slice(
|
|
243
|
-
configObject.getStart(sourceFile),
|
|
244
|
-
configObject.end
|
|
245
|
-
);
|
|
229
|
+
const existingPropertiesString = text.slice(configObject.getStart(sourceFile), configObject.end);
|
|
246
230
|
|
|
247
|
-
const multiline = existingPropertiesString.includes(
|
|
231
|
+
const multiline = existingPropertiesString.includes("\n");
|
|
248
232
|
if (multiline) {
|
|
249
233
|
// Build and return a multi-line config object string
|
|
250
234
|
// that preserves the existing indentation.
|
|
@@ -252,10 +236,8 @@ function buildConfigText(sourceFile, member, methodNames, quote) {
|
|
|
252
236
|
const [firstMember] = existingMembers;
|
|
253
237
|
const propertyIndent = firstMember
|
|
254
238
|
? getIndent(text, firstMember.getStart(sourceFile))
|
|
255
|
-
: memberIndent +
|
|
256
|
-
const content = propertyStrings
|
|
257
|
-
.map(part => `${propertyIndent}${part}`)
|
|
258
|
-
.join(',\n');
|
|
239
|
+
: memberIndent + " ";
|
|
240
|
+
const content = propertyStrings.map((part) => `${propertyIndent}${part}`).join(",\n");
|
|
259
241
|
return `{\n${content}\n${memberIndent}}`;
|
|
260
242
|
}
|
|
261
243
|
|
|
@@ -263,19 +245,16 @@ function buildConfigText(sourceFile, member, methodNames, quote) {
|
|
|
263
245
|
// that preserves the existing spacing around curly braces.
|
|
264
246
|
const openMatch = existingPropertiesString.match(/^\{(\s*)/);
|
|
265
247
|
const closeMatch = existingPropertiesString.match(/(\s*)\}$/);
|
|
266
|
-
const openSpacing = openMatch ? openMatch[1] :
|
|
267
|
-
const closeSpacing = closeMatch ? closeMatch[1] :
|
|
268
|
-
return `{${openSpacing}${propertyStrings.join(
|
|
248
|
+
const openSpacing = openMatch ? openMatch[1] : " ";
|
|
249
|
+
const closeSpacing = closeMatch ? closeMatch[1] : " ";
|
|
250
|
+
return `{${openSpacing}${propertyStrings.join(", ")}${closeSpacing}}`;
|
|
269
251
|
}
|
|
270
252
|
|
|
271
253
|
// Walks a method or accessor body to collect component property reads
|
|
272
254
|
// and dependency targets reached through `this`.
|
|
273
255
|
// This is only called by getMethodUsages.
|
|
274
256
|
function collectMethodBodyUsage(node, getterNames, props, calledMethods) {
|
|
275
|
-
if (
|
|
276
|
-
ts.isPropertyAccessExpression(node) &&
|
|
277
|
-
node.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
278
|
-
) {
|
|
257
|
+
if (ts.isPropertyAccessExpression(node) && node.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
279
258
|
// Handles direct property access like `this.foo`
|
|
280
259
|
// and records method calls like `this.foo()`.
|
|
281
260
|
recordThisAccess(props, calledMethods, getterNames, node, node.name.text);
|
|
@@ -287,13 +266,7 @@ function collectMethodBodyUsage(node, getterNames, props, calledMethods) {
|
|
|
287
266
|
) {
|
|
288
267
|
// Handles string-based element access like `this['foo']`
|
|
289
268
|
// and records method calls like `this['foo']()`.
|
|
290
|
-
recordThisAccess(
|
|
291
|
-
props,
|
|
292
|
-
calledMethods,
|
|
293
|
-
getterNames,
|
|
294
|
-
node,
|
|
295
|
-
node.argumentExpression.text
|
|
296
|
-
);
|
|
269
|
+
recordThisAccess(props, calledMethods, getterNames, node, node.argumentExpression.text);
|
|
297
270
|
} else if (
|
|
298
271
|
ts.isVariableDeclaration(node) &&
|
|
299
272
|
node.initializer &&
|
|
@@ -311,10 +284,7 @@ function collectMethodBodyUsage(node, getterNames, props, calledMethods) {
|
|
|
311
284
|
// or object-literal patterns that pull named properties from it.
|
|
312
285
|
if (ts.isObjectLiteralExpression(node.left)) {
|
|
313
286
|
for (const property of node.left.properties) {
|
|
314
|
-
if (
|
|
315
|
-
ts.isShorthandPropertyAssignment(property) ||
|
|
316
|
-
ts.isPropertyAssignment(property)
|
|
317
|
-
) {
|
|
287
|
+
if (ts.isShorthandPropertyAssignment(property) || ts.isPropertyAssignment(property)) {
|
|
318
288
|
const name = getNameText(property.name);
|
|
319
289
|
if (name) props.add(name);
|
|
320
290
|
}
|
|
@@ -324,8 +294,8 @@ function collectMethodBodyUsage(node, getterNames, props, calledMethods) {
|
|
|
324
294
|
}
|
|
325
295
|
}
|
|
326
296
|
|
|
327
|
-
ts.forEachChild(node, child =>
|
|
328
|
-
collectMethodBodyUsage(child, getterNames, props, calledMethods)
|
|
297
|
+
ts.forEachChild(node, (child) =>
|
|
298
|
+
collectMethodBodyUsage(child, getterNames, props, calledMethods),
|
|
329
299
|
);
|
|
330
300
|
}
|
|
331
301
|
|
|
@@ -334,7 +304,7 @@ function createUsedByProperty(methodNames, quote) {
|
|
|
334
304
|
const value =
|
|
335
305
|
methodNames.length === 1
|
|
336
306
|
? `${quote}${methodNames[0]}${quote}`
|
|
337
|
-
: `[${methodNames.map(name => `${quote}${name}${quote}`).join(
|
|
307
|
+
: `[${methodNames.map((name) => `${quote}${name}${quote}`).join(", ")}]`;
|
|
338
308
|
return `usedBy: ${value}`;
|
|
339
309
|
}
|
|
340
310
|
|
|
@@ -342,23 +312,23 @@ function createUsedByProperty(methodNames, quote) {
|
|
|
342
312
|
// the usedBy properties in property configuration objects
|
|
343
313
|
// found in the file at a given relative path.
|
|
344
314
|
export function evaluateSourceFile(filePath, options = {}) {
|
|
345
|
-
const {dry = false} = options;
|
|
315
|
+
const { dry = false } = options;
|
|
346
316
|
const absFilePath = path.resolve(cwd, filePath);
|
|
347
317
|
validateFile(absFilePath);
|
|
348
318
|
|
|
349
319
|
// The file is read in this function instead of in evaluateSourceText
|
|
350
320
|
// so unit tests can pass hard-coded text to that function.
|
|
351
|
-
const text = fs.readFileSync(absFilePath,
|
|
321
|
+
const text = fs.readFileSync(absFilePath, "utf8");
|
|
352
322
|
let {
|
|
353
323
|
changed,
|
|
354
324
|
foundWrecSubclass,
|
|
355
325
|
suggestions,
|
|
356
|
-
text: nextText
|
|
326
|
+
text: nextText,
|
|
357
327
|
} = evaluateSourceText(absFilePath, text);
|
|
358
328
|
|
|
359
329
|
// If we didn't find the definition of a class that extends Wrec ...
|
|
360
330
|
if (!foundWrecSubclass) {
|
|
361
|
-
throw new Error(
|
|
331
|
+
throw new Error("No class extending Wrec was found.");
|
|
362
332
|
}
|
|
363
333
|
|
|
364
334
|
// If this isn't a dry run and changes were made,
|
|
@@ -368,7 +338,7 @@ export function evaluateSourceFile(filePath, options = {}) {
|
|
|
368
338
|
suggestions = []; // all the suggestions have been applied
|
|
369
339
|
}
|
|
370
340
|
|
|
371
|
-
return {changed, foundWrecSubclass, suggestions, text: nextText};
|
|
341
|
+
return { changed, foundWrecSubclass, suggestions, text: nextText };
|
|
372
342
|
}
|
|
373
343
|
|
|
374
344
|
// Determines what changes, if any, should be made in
|
|
@@ -377,16 +347,8 @@ export function evaluateSourceFile(filePath, options = {}) {
|
|
|
377
347
|
// This function was factored out of evaluateSourceFile
|
|
378
348
|
// to support unit tests.
|
|
379
349
|
export function evaluateSourceText(filePath, text) {
|
|
380
|
-
const scriptKind = filePath.endsWith(
|
|
381
|
-
|
|
382
|
-
: ts.ScriptKind.JS;
|
|
383
|
-
const sourceFile = ts.createSourceFile(
|
|
384
|
-
filePath,
|
|
385
|
-
text,
|
|
386
|
-
ts.ScriptTarget.Latest,
|
|
387
|
-
true,
|
|
388
|
-
scriptKind
|
|
389
|
-
);
|
|
350
|
+
const scriptKind = filePath.endsWith(".ts") ? ts.ScriptKind.TS : ts.ScriptKind.JS;
|
|
351
|
+
const sourceFile = ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true, scriptKind);
|
|
390
352
|
return analyzeSourceFile(sourceFile);
|
|
391
353
|
}
|
|
392
354
|
|
|
@@ -403,11 +365,11 @@ function getCssCalledMethods(classNode) {
|
|
|
403
365
|
if (
|
|
404
366
|
ts.isPropertyDeclaration(member) &&
|
|
405
367
|
hasStaticModifier(member) &&
|
|
406
|
-
getNameText(member.name) ===
|
|
368
|
+
getNameText(member.name) === "css" &&
|
|
407
369
|
member.initializer &&
|
|
408
370
|
ts.isTaggedTemplateExpression(member.initializer) &&
|
|
409
371
|
ts.isIdentifier(member.initializer.tag) &&
|
|
410
|
-
member.initializer.tag.text ===
|
|
372
|
+
member.initializer.tag.text === "css"
|
|
411
373
|
) {
|
|
412
374
|
// Keep the last matching declaration because
|
|
413
375
|
// JavaScript class fields use last-one-wins semantics
|
|
@@ -441,7 +403,7 @@ function getComputedCalledMethods(classNode) {
|
|
|
441
403
|
if (
|
|
442
404
|
ts.isPropertyDeclaration(member) &&
|
|
443
405
|
hasStaticModifier(member) &&
|
|
444
|
-
getNameText(member.name) ===
|
|
406
|
+
getNameText(member.name) === "properties" &&
|
|
445
407
|
member.initializer &&
|
|
446
408
|
ts.isObjectLiteralExpression(member.initializer)
|
|
447
409
|
) {
|
|
@@ -459,10 +421,7 @@ function getComputedCalledMethods(classNode) {
|
|
|
459
421
|
for (const property of propertiesNode.properties) {
|
|
460
422
|
// If it isn't a property assignment or
|
|
461
423
|
// the property value isn't an object literal then skip it.
|
|
462
|
-
if (
|
|
463
|
-
!ts.isPropertyAssignment(property) ||
|
|
464
|
-
!ts.isObjectLiteralExpression(property.initializer)
|
|
465
|
-
) {
|
|
424
|
+
if (!ts.isPropertyAssignment(property) || !ts.isObjectLiteralExpression(property.initializer)) {
|
|
466
425
|
continue;
|
|
467
426
|
}
|
|
468
427
|
|
|
@@ -472,7 +431,7 @@ function getComputedCalledMethods(classNode) {
|
|
|
472
431
|
// the property name isn't "computed" then skip it.
|
|
473
432
|
if (
|
|
474
433
|
!ts.isPropertyAssignment(configProperty) ||
|
|
475
|
-
getNameText(configProperty.name) !==
|
|
434
|
+
getNameText(configProperty.name) !== "computed"
|
|
476
435
|
) {
|
|
477
436
|
continue;
|
|
478
437
|
}
|
|
@@ -512,9 +471,9 @@ function getGetterDependency(name) {
|
|
|
512
471
|
// Returns the leading indentation in the line
|
|
513
472
|
// that begins at a given position (`pos`) inside `text`.
|
|
514
473
|
function getIndent(text, pos) {
|
|
515
|
-
const lineStart = text.lastIndexOf(
|
|
474
|
+
const lineStart = text.lastIndexOf("\n", pos - 1) + 1;
|
|
516
475
|
const match = /^[ \t]*/.exec(text.slice(lineStart));
|
|
517
|
-
return match ? match[0] :
|
|
476
|
+
return match ? match[0] : "";
|
|
518
477
|
}
|
|
519
478
|
|
|
520
479
|
// Returns a map where the keys are property names and
|
|
@@ -543,7 +502,7 @@ function getMethodUsages(classNode, propertyNames) {
|
|
|
543
502
|
methodInfo.set(methodName, {
|
|
544
503
|
calledMethods,
|
|
545
504
|
isPrivate: ts.isPrivateIdentifier(member.name),
|
|
546
|
-
props
|
|
505
|
+
props,
|
|
547
506
|
});
|
|
548
507
|
}
|
|
549
508
|
|
|
@@ -553,7 +512,7 @@ function getMethodUsages(classNode, propertyNames) {
|
|
|
553
512
|
const entryMethods = new Set([
|
|
554
513
|
...getCssCalledMethods(classNode),
|
|
555
514
|
...getTemplateCalledMethods(classNode),
|
|
556
|
-
...getComputedCalledMethods(classNode)
|
|
515
|
+
...getComputedCalledMethods(classNode),
|
|
557
516
|
]);
|
|
558
517
|
const memo = new Map();
|
|
559
518
|
|
|
@@ -589,14 +548,14 @@ function getTemplateCalledMethods(classNode) {
|
|
|
589
548
|
if (
|
|
590
549
|
ts.isPropertyDeclaration(member) &&
|
|
591
550
|
hasStaticModifier(member) &&
|
|
592
|
-
getNameText(member.name) ===
|
|
551
|
+
getNameText(member.name) === "html" &&
|
|
593
552
|
member.initializer
|
|
594
553
|
) {
|
|
595
554
|
// If the value is a tagged template literal with the "html" tag ...
|
|
596
555
|
if (
|
|
597
556
|
ts.isTaggedTemplateExpression(member.initializer) &&
|
|
598
557
|
ts.isIdentifier(member.initializer.tag) &&
|
|
599
|
-
member.initializer.tag.text ===
|
|
558
|
+
member.initializer.tag.text === "html"
|
|
600
559
|
) {
|
|
601
560
|
template = member.initializer.template;
|
|
602
561
|
}
|
|
@@ -628,12 +587,7 @@ function getTransitiveProps(methodInfo, memo, methodName, seen = new Set()) {
|
|
|
628
587
|
|
|
629
588
|
if (info) {
|
|
630
589
|
for (const calledMethod of info.calledMethods) {
|
|
631
|
-
const calledProps = getTransitiveProps(
|
|
632
|
-
methodInfo,
|
|
633
|
-
memo,
|
|
634
|
-
calledMethod,
|
|
635
|
-
seen
|
|
636
|
-
);
|
|
590
|
+
const calledProps = getTransitiveProps(methodInfo, memo, calledMethod, seen);
|
|
637
591
|
for (const propName of calledProps) props.add(propName);
|
|
638
592
|
}
|
|
639
593
|
}
|
|
@@ -666,32 +620,35 @@ function isInstanceMethodMember(member) {
|
|
|
666
620
|
function isSupportedSourceFile(filePath, excludeTests = false) {
|
|
667
621
|
return (
|
|
668
622
|
/\.(js|ts)$/.test(filePath) &&
|
|
669
|
-
|
|
670
|
-
(!excludeTests || !filePath.includes(
|
|
623
|
+
!filePath.endsWith(".d.ts") &&
|
|
624
|
+
(!excludeTests || !filePath.includes(".test."))
|
|
671
625
|
);
|
|
672
626
|
}
|
|
673
627
|
|
|
674
628
|
// Handles CLI arguments and runs the script.
|
|
675
629
|
function main() {
|
|
676
630
|
const args = process.argv.slice(2);
|
|
677
|
-
const unknownFlags = args.filter(
|
|
678
|
-
arg => arg.startsWith('--') && arg !== '--dry'
|
|
679
|
-
);
|
|
631
|
+
const unknownFlags = args.filter((arg) => arg.startsWith("--") && arg !== "--dry");
|
|
680
632
|
if (unknownFlags.length > 0) {
|
|
681
633
|
throw new Error(`unknown option: ${unknownFlags[0]}`);
|
|
682
634
|
}
|
|
683
635
|
|
|
684
|
-
const inputPaths = args.filter(arg => !arg.startsWith(
|
|
636
|
+
const inputPaths = args.filter((arg) => !arg.startsWith("--"));
|
|
685
637
|
|
|
686
638
|
if (inputPaths.length !== 1) {
|
|
687
|
-
throw new Error(
|
|
639
|
+
throw new Error("Specify a single source file");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const inputPath = inputPaths[0];
|
|
643
|
+
if (!isSupportedSourceFile(inputPath)) {
|
|
644
|
+
throw new Error("unsupported file type");
|
|
688
645
|
}
|
|
689
646
|
|
|
690
|
-
const dry = args.includes(
|
|
691
|
-
const result = evaluateSourceFile(
|
|
647
|
+
const dry = args.includes("--dry");
|
|
648
|
+
const result = evaluateSourceFile(inputPath, { dry });
|
|
692
649
|
if (dry) {
|
|
693
650
|
// Report the proposed changes.
|
|
694
|
-
for (const {propName, suggestion} of result.suggestions) {
|
|
651
|
+
for (const { propName, suggestion } of result.suggestions) {
|
|
695
652
|
console.info(`${propName} - ${suggestion}`);
|
|
696
653
|
}
|
|
697
654
|
|
|
@@ -699,9 +656,9 @@ function main() {
|
|
|
699
656
|
// so that condition can be checked.
|
|
700
657
|
if (result.changed) process.exit(1);
|
|
701
658
|
} else if (result.changed) {
|
|
702
|
-
console.info(
|
|
659
|
+
console.info("updated source file");
|
|
703
660
|
} else {
|
|
704
|
-
console.info(
|
|
661
|
+
console.info("no changes needed");
|
|
705
662
|
}
|
|
706
663
|
}
|
|
707
664
|
|
|
@@ -718,13 +675,13 @@ function recordThisAccess(props, calledMethods, getterNames, node, name) {
|
|
|
718
675
|
|
|
719
676
|
// Validates that a source file exists and has a supported extension.
|
|
720
677
|
function validateFile(absFilePath) {
|
|
721
|
-
if (!fs.existsSync(absFilePath)) throw new Error(
|
|
678
|
+
if (!fs.existsSync(absFilePath)) throw new Error("File not found");
|
|
722
679
|
|
|
723
680
|
const stat = fs.statSync(absFilePath);
|
|
724
|
-
if (!stat.isFile()) throw new Error(
|
|
681
|
+
if (!stat.isFile()) throw new Error("Not a file");
|
|
725
682
|
|
|
726
683
|
if (!/\.(js|ts)$/.test(absFilePath)) {
|
|
727
|
-
throw new Error(
|
|
684
|
+
throw new Error("Unsupported file type");
|
|
728
685
|
}
|
|
729
686
|
}
|
|
730
687
|
|