wrec 0.34.0 → 0.35.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/package.json +3 -1
- package/scripts/declare.js +274 -0
- package/scripts/lint.js +110 -86
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "wrec",
|
|
3
3
|
"description": "a library that greatly simplifies building web components",
|
|
4
4
|
"author": "R. Mark Volkmann",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.35.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"bin": {
|
|
32
|
+
"wrec-declare": "./scripts/declare.js",
|
|
32
33
|
"wrec-lint": "./scripts/lint.js",
|
|
33
34
|
"wrec-scaffold": "./scripts/scaffold.js",
|
|
34
35
|
"wrec-usedby": "./scripts/used-by.js"
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"files": [
|
|
37
38
|
"dist",
|
|
38
39
|
"scripts/ast-utils.js",
|
|
40
|
+
"scripts/declare.js",
|
|
39
41
|
"scripts/lint.js",
|
|
40
42
|
"scripts/scaffold.js",
|
|
41
43
|
"scripts/template.ts",
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// This script inspects a given Wrec component source file and
|
|
4
|
+
// generates TypeScript `declare` statements for the properties
|
|
5
|
+
// found in the component's `static properties` declaration.
|
|
6
|
+
//
|
|
7
|
+
// To run this, enter `npx wrec-declare [--dry] {file-path}`
|
|
8
|
+
//
|
|
9
|
+
// Include the --dry flag to report whether changes are needed
|
|
10
|
+
// without writing the modified source back to the file.
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import ts from 'typescript';
|
|
15
|
+
import {
|
|
16
|
+
collectWrecClasses,
|
|
17
|
+
getMemberName,
|
|
18
|
+
hasStaticModifier
|
|
19
|
+
} from './ast-utils.js';
|
|
20
|
+
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const DECLARE_TYPE_MAP = new Map([
|
|
23
|
+
['Array', 'unknown[]'],
|
|
24
|
+
['Boolean', 'boolean'],
|
|
25
|
+
['Number', 'number'],
|
|
26
|
+
['Object', 'object'],
|
|
27
|
+
['String', 'string']
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
// Analyzes a parsed source file and returns any proposed `declare` edits.
|
|
31
|
+
function analyzeSourceFile(sourceFile) {
|
|
32
|
+
const edits = [];
|
|
33
|
+
const classNodes = collectWrecClasses(sourceFile);
|
|
34
|
+
|
|
35
|
+
for (const classNode of classNodes) {
|
|
36
|
+
const propertiesMember = getLastPropertiesDeclaration(classNode);
|
|
37
|
+
if (!propertiesMember) continue;
|
|
38
|
+
|
|
39
|
+
const declareLines = [];
|
|
40
|
+
for (const property of propertiesMember.initializer.properties) {
|
|
41
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
42
|
+
|
|
43
|
+
const propName = getMemberName(property);
|
|
44
|
+
if (!propName || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propName)) continue;
|
|
45
|
+
|
|
46
|
+
const declareType = getDeclareType(property.initializer);
|
|
47
|
+
if (!declareType) continue;
|
|
48
|
+
declareLines.push(createDeclareLine(propName, declareType));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const start = propertiesMember.end;
|
|
52
|
+
const end = findDeclareBlockEnd(classNode, propertiesMember, sourceFile);
|
|
53
|
+
const nextText = buildDeclareBlock(
|
|
54
|
+
sourceFile,
|
|
55
|
+
classNode,
|
|
56
|
+
propertiesMember,
|
|
57
|
+
declareLines
|
|
58
|
+
);
|
|
59
|
+
const currentText = sourceFile.text.slice(start, end);
|
|
60
|
+
|
|
61
|
+
if (nextText !== currentText) {
|
|
62
|
+
edits.push({end, start, text: nextText});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (classNodes.length === 0) {
|
|
67
|
+
return {changed: false, foundWrecSubclass: false, text: sourceFile.text};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (edits.length === 0) {
|
|
71
|
+
return {changed: false, foundWrecSubclass: true, text: sourceFile.text};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let nextSource = sourceFile.text;
|
|
75
|
+
edits.sort((a, b) => b.start - a.start);
|
|
76
|
+
|
|
77
|
+
for (const edit of edits) {
|
|
78
|
+
nextSource =
|
|
79
|
+
nextSource.slice(0, edit.start) + edit.text + nextSource.slice(edit.end);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {changed: true, foundWrecSubclass: true, text: nextSource};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Builds the `declare` block that should appear after `static properties`.
|
|
86
|
+
function buildDeclareBlock(sourceFile, classNode, propertiesMember, declareLines) {
|
|
87
|
+
const {text} = sourceFile;
|
|
88
|
+
const memberIndent = getIndent(text, propertiesMember.getStart(sourceFile));
|
|
89
|
+
const startIndex = classNode.members.indexOf(propertiesMember) + 1;
|
|
90
|
+
let nextMember = null;
|
|
91
|
+
|
|
92
|
+
for (let index = startIndex; index < classNode.members.length; index += 1) {
|
|
93
|
+
const member = classNode.members[index];
|
|
94
|
+
if (isDeclarePropertyDeclaration(member)) continue;
|
|
95
|
+
|
|
96
|
+
nextMember = member;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nextIndent = nextMember
|
|
101
|
+
? getIndent(text, nextMember.getStart(sourceFile))
|
|
102
|
+
: '';
|
|
103
|
+
|
|
104
|
+
if (declareLines.length === 0) {
|
|
105
|
+
return nextMember ? `\n\n${nextIndent}` : '\n';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const content = declareLines
|
|
109
|
+
.map(line => `${memberIndent}${line}`)
|
|
110
|
+
.join('\n');
|
|
111
|
+
return nextMember ? `\n${content}\n\n${nextIndent}` : `\n${content}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Creates a single `declare` statement for a component property.
|
|
115
|
+
function createDeclareLine(propName, declareType) {
|
|
116
|
+
return `declare ${propName}: ${declareType};`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Determines what changes, if any, should be made in a source file.
|
|
120
|
+
export function evaluateSourceFile(filePath, options = {}) {
|
|
121
|
+
const {dry = false} = options;
|
|
122
|
+
const absFilePath = path.resolve(cwd, filePath);
|
|
123
|
+
validateFile(absFilePath);
|
|
124
|
+
|
|
125
|
+
const text = fs.readFileSync(absFilePath, 'utf8');
|
|
126
|
+
const result = evaluateSourceText(absFilePath, text);
|
|
127
|
+
|
|
128
|
+
if (!result.foundWrecSubclass) {
|
|
129
|
+
throw new Error('No class extending Wrec was found.');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!dry && result.changed) {
|
|
133
|
+
fs.writeFileSync(absFilePath, result.text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Determines what changes, if any, should be made in source file text.
|
|
140
|
+
export function evaluateSourceText(filePath, text) {
|
|
141
|
+
const scriptKind = filePath.endsWith('.ts')
|
|
142
|
+
? ts.ScriptKind.TS
|
|
143
|
+
: ts.ScriptKind.JS;
|
|
144
|
+
const sourceFile = ts.createSourceFile(
|
|
145
|
+
filePath,
|
|
146
|
+
text,
|
|
147
|
+
ts.ScriptTarget.Latest,
|
|
148
|
+
true,
|
|
149
|
+
scriptKind
|
|
150
|
+
);
|
|
151
|
+
return analyzeSourceFile(sourceFile);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Finds the end of the contiguous `declare` block after `static properties`.
|
|
155
|
+
function findDeclareBlockEnd(classNode, propertiesMember, sourceFile) {
|
|
156
|
+
const startIndex = classNode.members.indexOf(propertiesMember) + 1;
|
|
157
|
+
|
|
158
|
+
for (let index = startIndex; index < classNode.members.length; index += 1) {
|
|
159
|
+
const member = classNode.members[index];
|
|
160
|
+
if (isDeclarePropertyDeclaration(member)) continue;
|
|
161
|
+
return member.getStart(sourceFile);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return classNode.end - 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Gets the TypeScript type string for a property configuration object.
|
|
168
|
+
function getDeclareType(initializer) {
|
|
169
|
+
if (!ts.isObjectLiteralExpression(initializer)) return null;
|
|
170
|
+
|
|
171
|
+
for (const property of initializer.properties) {
|
|
172
|
+
if (
|
|
173
|
+
!ts.isPropertyAssignment(property) ||
|
|
174
|
+
getMemberName(property) !== 'type' ||
|
|
175
|
+
!ts.isIdentifier(property.initializer)
|
|
176
|
+
) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return DECLARE_TYPE_MAP.get(property.initializer.text) ?? null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Gets the leading indentation in the line that contains a given position.
|
|
187
|
+
function getIndent(text, pos) {
|
|
188
|
+
const lineStart = text.lastIndexOf('\n', pos - 1) + 1;
|
|
189
|
+
const match = /^[ \t]*/.exec(text.slice(lineStart));
|
|
190
|
+
return match ? match[0] : '';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Gets the last `static properties = { ... }` declaration in a class.
|
|
194
|
+
function getLastPropertiesDeclaration(classNode) {
|
|
195
|
+
let propertiesMember = null;
|
|
196
|
+
|
|
197
|
+
for (const member of classNode.members) {
|
|
198
|
+
if (
|
|
199
|
+
ts.isPropertyDeclaration(member) &&
|
|
200
|
+
hasStaticModifier(member) &&
|
|
201
|
+
getMemberName(member) === 'properties' &&
|
|
202
|
+
member.initializer &&
|
|
203
|
+
ts.isObjectLiteralExpression(member.initializer)
|
|
204
|
+
) {
|
|
205
|
+
propertiesMember = member;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return propertiesMember;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Determines whether a class member is a `declare` property declaration.
|
|
213
|
+
function isDeclarePropertyDeclaration(member) {
|
|
214
|
+
return (
|
|
215
|
+
ts.isPropertyDeclaration(member) &&
|
|
216
|
+
ts.canHaveModifiers(member) &&
|
|
217
|
+
ts
|
|
218
|
+
.getModifiers(member)
|
|
219
|
+
?.some(mod => mod.kind === ts.SyntaxKind.DeclareKeyword) === true
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Handles CLI arguments and runs the script.
|
|
224
|
+
function main() {
|
|
225
|
+
const args = process.argv.slice(2);
|
|
226
|
+
const unknownFlags = args.filter(
|
|
227
|
+
arg => arg.startsWith('--') && arg !== '--dry'
|
|
228
|
+
);
|
|
229
|
+
if (unknownFlags.length > 0) {
|
|
230
|
+
throw new Error(`unknown option: ${unknownFlags[0]}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const inputPaths = args.filter(arg => !arg.startsWith('--'));
|
|
234
|
+
if (inputPaths.length !== 1) {
|
|
235
|
+
throw new Error('Specify a single source file');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const dry = args.includes('--dry');
|
|
239
|
+
const result = evaluateSourceFile(inputPaths[0], {dry});
|
|
240
|
+
|
|
241
|
+
if (dry) {
|
|
242
|
+
if (result.changed) process.exit(1);
|
|
243
|
+
console.info('no changes needed');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (result.changed) {
|
|
248
|
+
console.info('updated source file');
|
|
249
|
+
} else {
|
|
250
|
+
console.info('no changes needed');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Validates that a source file exists and has a supported extension.
|
|
255
|
+
function validateFile(absFilePath) {
|
|
256
|
+
if (!fs.existsSync(absFilePath)) throw new Error('File not found');
|
|
257
|
+
|
|
258
|
+
const stat = fs.statSync(absFilePath);
|
|
259
|
+
if (!stat.isFile()) throw new Error('Not a file');
|
|
260
|
+
|
|
261
|
+
if (!/\.(js|ts)$/.test(absFilePath) || /\.d\.ts$/.test(absFilePath)) {
|
|
262
|
+
throw new Error('Unsupported file type');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Runs the CLI when this module is executed directly.
|
|
267
|
+
if (import.meta.main) {
|
|
268
|
+
try {
|
|
269
|
+
main();
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
package/scripts/lint.js
CHANGED
|
@@ -119,6 +119,13 @@ const THIS_CALL_RE = /this\.([A-Za-z_$][\w$]*)\s*\(/g;
|
|
|
119
119
|
const THIS_REF_RE = /this\.([A-Za-z_$][\w$]*)(\.[A-Za-z_$][\w$]*)*/g;
|
|
120
120
|
const PLACEHOLDER_PREFIX = '__WREC_PLACEHOLDER__';
|
|
121
121
|
const RESERVED_PROPERTY_NAMES = new Set(['class', 'style']);
|
|
122
|
+
const SUPPORTED_PROPERTY_TYPE_NAMES = new Set([
|
|
123
|
+
'Array',
|
|
124
|
+
'Boolean',
|
|
125
|
+
'Number',
|
|
126
|
+
'Object',
|
|
127
|
+
'String'
|
|
128
|
+
]);
|
|
122
129
|
const GETTER_PREFIX = 'get ';
|
|
123
130
|
const SUPPORTED_EVENT_NAMES = new Set([
|
|
124
131
|
'blur',
|
|
@@ -806,27 +813,28 @@ function formatReport(
|
|
|
806
813
|
|
|
807
814
|
const hasIssues =
|
|
808
815
|
findings.duplicateProperties.length > 0 ||
|
|
809
|
-
findings.
|
|
810
|
-
findings.
|
|
816
|
+
findings.extraArguments.length > 0 ||
|
|
817
|
+
findings.incompatibleArguments.length > 0 ||
|
|
811
818
|
findings.invalidCheckedBindings.length > 0 ||
|
|
812
819
|
findings.invalidComputedProperties.length > 0 ||
|
|
813
|
-
findings.invalidRefAttributes.length > 0 ||
|
|
814
|
-
findings.invalidValuesConfigurations.length > 0 ||
|
|
815
820
|
findings.invalidDefaultValues.length > 0 ||
|
|
821
|
+
findings.invalidEventHandlers.length > 0 ||
|
|
816
822
|
findings.invalidFormAssocValues.length > 0 ||
|
|
823
|
+
findings.invalidHtmlNesting.length > 0 ||
|
|
824
|
+
findings.invalidRefAttributes.length > 0 ||
|
|
825
|
+
findings.invalidTypeProperties.length > 0 ||
|
|
826
|
+
findings.invalidUsedByReferences.length > 0 ||
|
|
817
827
|
findings.invalidUseStateMaps.length > 0 ||
|
|
828
|
+
findings.invalidValuesConfigurations.length > 0 ||
|
|
818
829
|
findings.missingFormAssociatedProperty.length > 0 ||
|
|
819
830
|
findings.missingTypeProperties.length > 0 ||
|
|
820
|
-
findings.
|
|
831
|
+
findings.reservedProperties.length > 0 ||
|
|
832
|
+
findings.typeErrors.length > 0 ||
|
|
821
833
|
findings.undefinedContextFunctions.length > 0 ||
|
|
822
834
|
findings.undefinedMethods.length > 0 ||
|
|
823
|
-
findings.
|
|
824
|
-
findings.incompatibleArguments.length > 0 ||
|
|
825
|
-
findings.invalidEventHandlers.length > 0 ||
|
|
826
|
-
findings.invalidHtmlNesting.length > 0 ||
|
|
827
|
-
findings.unsupportedHtmlAttributes.length > 0 ||
|
|
835
|
+
findings.undefinedProperties.length > 0 ||
|
|
828
836
|
findings.unsupportedEventNames.length > 0 ||
|
|
829
|
-
findings.
|
|
837
|
+
findings.unsupportedHtmlAttributes.length > 0;
|
|
830
838
|
|
|
831
839
|
if (showFileHeader) lines.push(`file: ${fileLabel}`);
|
|
832
840
|
|
|
@@ -859,14 +867,31 @@ function formatReport(
|
|
|
859
867
|
findings.duplicateProperties.forEach(name => lines.push(` ${name}`));
|
|
860
868
|
}
|
|
861
869
|
|
|
862
|
-
if (findings.
|
|
863
|
-
lines.push('
|
|
864
|
-
findings.
|
|
870
|
+
if (findings.extraArguments.length > 0) {
|
|
871
|
+
lines.push('extra arguments:');
|
|
872
|
+
findings.extraArguments.forEach(finding => {
|
|
873
|
+
lines.push(
|
|
874
|
+
` ${finding.methodName}: argument ${finding.argumentIndex} ` +
|
|
875
|
+
`"${finding.argument}" exceeds the ` +
|
|
876
|
+
`${finding.parameterCount}-parameter signature`
|
|
877
|
+
);
|
|
878
|
+
});
|
|
865
879
|
}
|
|
866
880
|
|
|
867
|
-
if (findings.
|
|
868
|
-
lines.push('
|
|
869
|
-
findings.
|
|
881
|
+
if (findings.incompatibleArguments.length > 0) {
|
|
882
|
+
lines.push('incompatible arguments:');
|
|
883
|
+
findings.incompatibleArguments.forEach(finding => {
|
|
884
|
+
lines.push(
|
|
885
|
+
` ${finding.methodName}: argument "${finding.argument}" ` +
|
|
886
|
+
`has type ${finding.argumentType}, but parameter ` +
|
|
887
|
+
`"${finding.parameterName}" expects ${finding.parameterType}`
|
|
888
|
+
);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (findings.invalidCheckedBindings.length > 0) {
|
|
893
|
+
lines.push('invalid checked bindings:');
|
|
894
|
+
findings.invalidCheckedBindings.forEach(message =>
|
|
870
895
|
lines.push(` ${message}`)
|
|
871
896
|
);
|
|
872
897
|
}
|
|
@@ -878,13 +903,32 @@ function formatReport(
|
|
|
878
903
|
);
|
|
879
904
|
}
|
|
880
905
|
|
|
881
|
-
if (findings.
|
|
882
|
-
lines.push('invalid
|
|
883
|
-
findings.
|
|
906
|
+
if (findings.invalidDefaultValues.length > 0) {
|
|
907
|
+
lines.push('invalid default values:');
|
|
908
|
+
findings.invalidDefaultValues.forEach(message =>
|
|
884
909
|
lines.push(` ${message}`)
|
|
885
910
|
);
|
|
886
911
|
}
|
|
887
912
|
|
|
913
|
+
if (findings.invalidEventHandlers.length > 0) {
|
|
914
|
+
lines.push('invalid event handler references:');
|
|
915
|
+
findings.invalidEventHandlers.forEach(message =>
|
|
916
|
+
lines.push(` ${message}`)
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (findings.invalidFormAssocValues.length > 0) {
|
|
921
|
+
lines.push('invalid form-assoc values:');
|
|
922
|
+
findings.invalidFormAssocValues.forEach(message =>
|
|
923
|
+
lines.push(` ${message}`)
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (findings.invalidHtmlNesting.length > 0) {
|
|
928
|
+
lines.push('invalid html nesting:');
|
|
929
|
+
findings.invalidHtmlNesting.forEach(message => lines.push(` ${message}`));
|
|
930
|
+
}
|
|
931
|
+
|
|
888
932
|
if (findings.invalidRefAttributes.length > 0) {
|
|
889
933
|
lines.push('invalid ref attributes:');
|
|
890
934
|
findings.invalidRefAttributes.forEach(message =>
|
|
@@ -892,23 +936,28 @@ function formatReport(
|
|
|
892
936
|
);
|
|
893
937
|
}
|
|
894
938
|
|
|
895
|
-
if (findings.
|
|
896
|
-
lines.push('invalid
|
|
897
|
-
findings.
|
|
939
|
+
if (findings.invalidTypeProperties.length > 0) {
|
|
940
|
+
lines.push('invalid type properties:');
|
|
941
|
+
findings.invalidTypeProperties.forEach(message =>
|
|
898
942
|
lines.push(` ${message}`)
|
|
899
943
|
);
|
|
900
944
|
}
|
|
901
945
|
|
|
902
|
-
if (findings.
|
|
903
|
-
lines.push('invalid
|
|
904
|
-
findings.
|
|
946
|
+
if (findings.invalidUsedByReferences.length > 0) {
|
|
947
|
+
lines.push('invalid usedBy references:');
|
|
948
|
+
findings.invalidUsedByReferences.forEach(message =>
|
|
905
949
|
lines.push(` ${message}`)
|
|
906
950
|
);
|
|
907
951
|
}
|
|
908
952
|
|
|
909
|
-
if (findings.
|
|
910
|
-
lines.push('invalid
|
|
911
|
-
findings.
|
|
953
|
+
if (findings.invalidUseStateMaps.length > 0) {
|
|
954
|
+
lines.push('invalid useState map entries:');
|
|
955
|
+
findings.invalidUseStateMaps.forEach(message => lines.push(` ${message}`));
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (findings.invalidValuesConfigurations.length > 0) {
|
|
959
|
+
lines.push('invalid values configurations:');
|
|
960
|
+
findings.invalidValuesConfigurations.forEach(message =>
|
|
912
961
|
lines.push(` ${message}`)
|
|
913
962
|
);
|
|
914
963
|
}
|
|
@@ -927,9 +976,16 @@ function formatReport(
|
|
|
927
976
|
);
|
|
928
977
|
}
|
|
929
978
|
|
|
930
|
-
if (findings.
|
|
931
|
-
lines.push('
|
|
932
|
-
findings.
|
|
979
|
+
if (findings.reservedProperties.length > 0) {
|
|
980
|
+
lines.push('reserved property names:');
|
|
981
|
+
findings.reservedProperties.forEach(name => lines.push(` ${name}`));
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (findings.typeErrors.length > 0) {
|
|
985
|
+
lines.push('type errors:');
|
|
986
|
+
findings.typeErrors.forEach(finding => {
|
|
987
|
+
lines.push(` ${finding.expression}: ${finding.message}`);
|
|
988
|
+
});
|
|
933
989
|
}
|
|
934
990
|
|
|
935
991
|
if (findings.undefinedContextFunctions.length > 0) {
|
|
@@ -942,50 +998,16 @@ function formatReport(
|
|
|
942
998
|
findings.undefinedMethods.forEach(name => lines.push(` ${name}`));
|
|
943
999
|
}
|
|
944
1000
|
|
|
945
|
-
if (findings.
|
|
946
|
-
lines.push('
|
|
947
|
-
findings.
|
|
948
|
-
lines.push(` ${message}`)
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (findings.invalidUseStateMaps.length > 0) {
|
|
953
|
-
lines.push('invalid useState map entries:');
|
|
954
|
-
findings.invalidUseStateMaps.forEach(message => lines.push(` ${message}`));
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (findings.invalidHtmlNesting.length > 0) {
|
|
958
|
-
lines.push('invalid html nesting:');
|
|
959
|
-
findings.invalidHtmlNesting.forEach(message => lines.push(` ${message}`));
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (findings.extraArguments.length > 0) {
|
|
963
|
-
lines.push('extra arguments:');
|
|
964
|
-
findings.extraArguments.forEach(finding => {
|
|
965
|
-
lines.push(
|
|
966
|
-
` ${finding.methodName}: argument ${finding.argumentIndex} ` +
|
|
967
|
-
`"${finding.argument}" exceeds the ` +
|
|
968
|
-
`${finding.parameterCount}-parameter signature`
|
|
969
|
-
);
|
|
970
|
-
});
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
if (findings.incompatibleArguments.length > 0) {
|
|
974
|
-
lines.push('incompatible arguments:');
|
|
975
|
-
findings.incompatibleArguments.forEach(finding => {
|
|
976
|
-
lines.push(
|
|
977
|
-
` ${finding.methodName}: argument "${finding.argument}" ` +
|
|
978
|
-
`has type ${finding.argumentType}, but parameter ` +
|
|
979
|
-
`"${finding.parameterName}" expects ${finding.parameterType}`
|
|
980
|
-
);
|
|
981
|
-
});
|
|
1001
|
+
if (findings.undefinedProperties.length > 0) {
|
|
1002
|
+
lines.push('undefined properties:');
|
|
1003
|
+
findings.undefinedProperties.forEach(name => lines.push(` ${name}`));
|
|
982
1004
|
}
|
|
983
1005
|
|
|
984
|
-
if (findings.
|
|
985
|
-
lines.push('
|
|
986
|
-
findings.
|
|
987
|
-
lines.push(` ${
|
|
988
|
-
|
|
1006
|
+
if (findings.unsupportedEventNames.length > 0) {
|
|
1007
|
+
lines.push('unsupported event names:');
|
|
1008
|
+
findings.unsupportedEventNames.forEach(message =>
|
|
1009
|
+
lines.push(` ${message}`)
|
|
1010
|
+
);
|
|
989
1011
|
}
|
|
990
1012
|
|
|
991
1013
|
if (findings.unsupportedHtmlAttributes.length > 0) {
|
|
@@ -995,13 +1017,6 @@ function formatReport(
|
|
|
995
1017
|
);
|
|
996
1018
|
}
|
|
997
1019
|
|
|
998
|
-
if (findings.unsupportedEventNames.length > 0) {
|
|
999
|
-
lines.push('unsupported event names:');
|
|
1000
|
-
findings.unsupportedEventNames.forEach(message =>
|
|
1001
|
-
lines.push(` ${message}`)
|
|
1002
|
-
);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
1020
|
if (!hasIssues && showNoIssuesMessage) lines.push('no issues found');
|
|
1006
1021
|
|
|
1007
1022
|
return `${lines.join('\n')}\n`;
|
|
@@ -1360,8 +1375,9 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1360
1375
|
invalidFormAssocValues: [],
|
|
1361
1376
|
invalidHtmlNesting: [],
|
|
1362
1377
|
invalidRefAttributes: [],
|
|
1363
|
-
|
|
1378
|
+
invalidTypeProperties: [],
|
|
1364
1379
|
invalidUsedByReferences: [],
|
|
1380
|
+
invalidUseStateMaps: [],
|
|
1365
1381
|
invalidValuesConfigurations: [],
|
|
1366
1382
|
missingFormAssociatedProperty: [],
|
|
1367
1383
|
missingTypeProperties: [],
|
|
@@ -1370,8 +1386,8 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1370
1386
|
undefinedContextFunctions: [],
|
|
1371
1387
|
undefinedMethods: [],
|
|
1372
1388
|
undefinedProperties: [],
|
|
1373
|
-
|
|
1374
|
-
|
|
1389
|
+
unsupportedEventNames: [],
|
|
1390
|
+
unsupportedHtmlAttributes: []
|
|
1375
1391
|
};
|
|
1376
1392
|
const templateExprs = extractTemplateExpressions(
|
|
1377
1393
|
classNode,
|
|
@@ -1464,8 +1480,9 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1464
1480
|
findings.invalidFormAssocValues.sort();
|
|
1465
1481
|
findings.invalidHtmlNesting.sort();
|
|
1466
1482
|
findings.invalidRefAttributes.sort();
|
|
1467
|
-
findings.
|
|
1483
|
+
findings.invalidTypeProperties.sort();
|
|
1468
1484
|
findings.invalidUsedByReferences.sort();
|
|
1485
|
+
findings.invalidUseStateMaps.sort();
|
|
1469
1486
|
findings.invalidValuesConfigurations.sort();
|
|
1470
1487
|
findings.missingFormAssociatedProperty.sort();
|
|
1471
1488
|
findings.missingTypeProperties.sort();
|
|
@@ -1474,8 +1491,8 @@ export function lintSource(filePath, sourceText, options = {}) {
|
|
|
1474
1491
|
findings.undefinedContextFunctions.sort();
|
|
1475
1492
|
findings.undefinedMethods.sort();
|
|
1476
1493
|
findings.undefinedProperties.sort();
|
|
1477
|
-
findings.unsupportedHtmlAttributes.sort();
|
|
1478
1494
|
findings.unsupportedEventNames.sort();
|
|
1495
|
+
findings.unsupportedHtmlAttributes.sort();
|
|
1479
1496
|
|
|
1480
1497
|
return formatReport(
|
|
1481
1498
|
filePath,
|
|
@@ -1870,6 +1887,13 @@ function validatePropertyConfigs(
|
|
|
1870
1887
|
findings.missingTypeProperties.push(
|
|
1871
1888
|
`property "${propName}" does not specify a type`
|
|
1872
1889
|
);
|
|
1890
|
+
} else if (
|
|
1891
|
+
!SUPPORTED_PROPERTY_TYPE_NAMES.has(typeExpressionKind(typeExpression))
|
|
1892
|
+
) {
|
|
1893
|
+
findings.invalidTypeProperties.push(
|
|
1894
|
+
`property "${propName}" type must be one of ` +
|
|
1895
|
+
'Boolean, Number, String, Object, or Array'
|
|
1896
|
+
);
|
|
1873
1897
|
}
|
|
1874
1898
|
|
|
1875
1899
|
if (usedByProp && ts.isPropertyAssignment(usedByProp)) {
|