@rettangoli/check 0.0.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.
- package/README.md +295 -0
- package/ROADMAP.md +175 -0
- package/package.json +46 -0
- package/src/cli/bin.js +325 -0
- package/src/cli/check.js +232 -0
- package/src/cli/index.js +1 -0
- package/src/core/analyze.js +227 -0
- package/src/core/discovery.js +83 -0
- package/src/core/exportedFunctions.js +235 -0
- package/src/core/model.js +898 -0
- package/src/core/parsers.js +2726 -0
- package/src/core/registry.js +779 -0
- package/src/core/schema.js +161 -0
- package/src/core/scopeGraph.js +1400 -0
- package/src/core/semantic.js +329 -0
- package/src/diagnostics/autofix.js +191 -0
- package/src/diagnostics/catalog.js +89 -0
- package/src/index.js +2 -0
- package/src/reporters/index.js +13 -0
- package/src/reporters/json.js +42 -0
- package/src/reporters/sarif.js +213 -0
- package/src/reporters/text.js +145 -0
- package/src/rules/compatibility.js +318 -0
- package/src/rules/constants.js +22 -0
- package/src/rules/crossFileSymbols.js +108 -0
- package/src/rules/expression.js +338 -0
- package/src/rules/feParity.js +65 -0
- package/src/rules/index.js +39 -0
- package/src/rules/jempl.js +80 -0
- package/src/rules/lifecycle.js +4 -0
- package/src/rules/listenerConfig.js +556 -0
- package/src/rules/listenerSymbols.js +49 -0
- package/src/rules/methods.js +117 -0
- package/src/rules/refs.js +20 -0
- package/src/rules/schema.js +118 -0
- package/src/rules/shared.js +20 -0
- package/src/rules/yahtmlAttrs.js +238 -0
- package/src/semantic/engine.js +778 -0
- package/src/semantic/index.js +9 -0
- package/src/types/lattice.js +281 -0
- package/src/utils/case.js +9 -0
- package/src/utils/fs.js +30 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
const PRIMITIVE_TYPE_ORDER = [
|
|
2
|
+
"unknown",
|
|
3
|
+
"null",
|
|
4
|
+
"boolean",
|
|
5
|
+
"number",
|
|
6
|
+
"string",
|
|
7
|
+
"object",
|
|
8
|
+
"array",
|
|
9
|
+
"function",
|
|
10
|
+
"any",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const PRIMITIVE_TYPE_SET = new Set(PRIMITIVE_TYPE_ORDER);
|
|
14
|
+
|
|
15
|
+
export const normalizePrimitiveType = (value) => {
|
|
16
|
+
if (!value) {
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalized = String(value).trim().toLowerCase();
|
|
21
|
+
if (!normalized) {
|
|
22
|
+
return "unknown";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (normalized === "integer") {
|
|
26
|
+
return "number";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (PRIMITIVE_TYPE_SET.has(normalized)) {
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return "unknown";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const schemaNodeToLatticeType = (schemaNode) => {
|
|
37
|
+
if (!schemaNode || typeof schemaNode !== "object" || Array.isArray(schemaNode)) {
|
|
38
|
+
return { kind: "unknown" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const toUnion = (options = []) => {
|
|
42
|
+
const normalized = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
|
|
45
|
+
options.forEach((option) => {
|
|
46
|
+
if (!option || typeof option !== "object") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (option.kind === "union" && Array.isArray(option.options)) {
|
|
50
|
+
option.options.forEach((nested) => {
|
|
51
|
+
if (!nested || typeof nested !== "object" || !nested.kind) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const key = JSON.stringify(nested);
|
|
55
|
+
if (seen.has(key)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
seen.add(key);
|
|
59
|
+
normalized.push(nested);
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!option.kind) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const key = JSON.stringify(option);
|
|
68
|
+
if (seen.has(key)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
seen.add(key);
|
|
72
|
+
normalized.push(option);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (normalized.length === 0) {
|
|
76
|
+
return { kind: "unknown" };
|
|
77
|
+
}
|
|
78
|
+
if (normalized.length === 1) {
|
|
79
|
+
return normalized[0];
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
kind: "union",
|
|
83
|
+
options: normalized,
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const explicitType = schemaNode.type;
|
|
88
|
+
if (Array.isArray(explicitType) && explicitType.length > 0) {
|
|
89
|
+
const options = explicitType.map((entry) => schemaNodeToLatticeType({
|
|
90
|
+
...schemaNode,
|
|
91
|
+
type: entry,
|
|
92
|
+
nullable: false,
|
|
93
|
+
anyOf: undefined,
|
|
94
|
+
oneOf: undefined,
|
|
95
|
+
allOf: undefined,
|
|
96
|
+
enum: undefined,
|
|
97
|
+
}));
|
|
98
|
+
if (schemaNode.nullable === true) {
|
|
99
|
+
options.push({ kind: "null" });
|
|
100
|
+
}
|
|
101
|
+
return toUnion(options);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(schemaNode.anyOf) && schemaNode.anyOf.length > 0) {
|
|
105
|
+
const options = schemaNode.anyOf.map((entry) => schemaNodeToLatticeType(entry));
|
|
106
|
+
if (schemaNode.nullable === true) {
|
|
107
|
+
options.push({ kind: "null" });
|
|
108
|
+
}
|
|
109
|
+
return toUnion(options);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(schemaNode.oneOf) && schemaNode.oneOf.length > 0) {
|
|
113
|
+
const options = schemaNode.oneOf.map((entry) => schemaNodeToLatticeType(entry));
|
|
114
|
+
if (schemaNode.nullable === true) {
|
|
115
|
+
options.push({ kind: "null" });
|
|
116
|
+
}
|
|
117
|
+
return toUnion(options);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(schemaNode.allOf) && schemaNode.allOf.length > 0) {
|
|
121
|
+
const options = schemaNode.allOf.map((entry) => schemaNodeToLatticeType(entry));
|
|
122
|
+
if (schemaNode.nullable === true) {
|
|
123
|
+
options.push({ kind: "null" });
|
|
124
|
+
}
|
|
125
|
+
return toUnion(options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (Array.isArray(schemaNode.enum) && schemaNode.enum.length > 0) {
|
|
129
|
+
const enumOptions = schemaNode.enum.map((value) => {
|
|
130
|
+
if (value === null) return { kind: "null" };
|
|
131
|
+
if (typeof value === "boolean") return { kind: "boolean" };
|
|
132
|
+
if (typeof value === "number") return { kind: "number" };
|
|
133
|
+
if (typeof value === "string") return { kind: "string" };
|
|
134
|
+
if (Array.isArray(value)) return { kind: "array" };
|
|
135
|
+
if (value && typeof value === "object") return { kind: "object" };
|
|
136
|
+
return { kind: "unknown" };
|
|
137
|
+
});
|
|
138
|
+
return toUnion(enumOptions);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const type = normalizePrimitiveType(schemaNode.type);
|
|
142
|
+
if (type === "array") {
|
|
143
|
+
const arrayType = {
|
|
144
|
+
kind: "array",
|
|
145
|
+
items: schemaNodeToLatticeType(schemaNode.items),
|
|
146
|
+
};
|
|
147
|
+
if (schemaNode.nullable === true) {
|
|
148
|
+
return toUnion([arrayType, { kind: "null" }]);
|
|
149
|
+
}
|
|
150
|
+
return arrayType;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (type === "object") {
|
|
154
|
+
const properties = schemaNode?.properties && typeof schemaNode.properties === "object" && !Array.isArray(schemaNode.properties)
|
|
155
|
+
? schemaNode.properties
|
|
156
|
+
: {};
|
|
157
|
+
const normalizedProperties = Object.entries(properties).reduce((result, [key, value]) => {
|
|
158
|
+
result[key] = schemaNodeToLatticeType(value);
|
|
159
|
+
return result;
|
|
160
|
+
}, {});
|
|
161
|
+
|
|
162
|
+
const objectType = {
|
|
163
|
+
kind: "object",
|
|
164
|
+
properties: normalizedProperties,
|
|
165
|
+
};
|
|
166
|
+
if (schemaNode.nullable === true) {
|
|
167
|
+
return toUnion([objectType, { kind: "null" }]);
|
|
168
|
+
}
|
|
169
|
+
return objectType;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const primitiveType = { kind: type };
|
|
173
|
+
if (schemaNode.nullable === true) {
|
|
174
|
+
return toUnion([primitiveType, { kind: "null" }]);
|
|
175
|
+
}
|
|
176
|
+
return primitiveType;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const inferLiteralLatticeType = (value = "") => {
|
|
180
|
+
const source = String(value || "").trim();
|
|
181
|
+
if (!source) {
|
|
182
|
+
return { kind: "unknown" };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (source === "null") {
|
|
186
|
+
return { kind: "null" };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (source === "true" || source === "false") {
|
|
190
|
+
return { kind: "boolean" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (/^[-+]?(?:\d+\.?\d*|\d*\.?\d+)$/u.test(source)) {
|
|
194
|
+
return { kind: "number" };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if ((source.startsWith("\"") && source.endsWith("\"")) || (source.startsWith("'") && source.endsWith("'"))) {
|
|
198
|
+
return { kind: "string" };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (source.startsWith("{") && source.endsWith("}")) {
|
|
202
|
+
return { kind: "object" };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (source.startsWith("[") && source.endsWith("]")) {
|
|
206
|
+
return { kind: "array" };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { kind: "unknown" };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const inferSchemaNodePrimitiveType = (schemaNode) => {
|
|
213
|
+
const latticeType = schemaNodeToLatticeType(schemaNode);
|
|
214
|
+
if (latticeType.kind === "union" && Array.isArray(latticeType.options)) {
|
|
215
|
+
const nonNull = latticeType.options.filter((option) => option?.kind && option.kind !== "null");
|
|
216
|
+
if (nonNull.length === 1) {
|
|
217
|
+
return nonNull[0].kind;
|
|
218
|
+
}
|
|
219
|
+
return "unknown";
|
|
220
|
+
}
|
|
221
|
+
return latticeType.kind || "unknown";
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export const areTypesCompatible = ({ expected = "unknown", actual = "unknown" }) => {
|
|
225
|
+
const flattenKinds = (value) => {
|
|
226
|
+
if (typeof value === "string") {
|
|
227
|
+
return new Set([normalizePrimitiveType(value)]);
|
|
228
|
+
}
|
|
229
|
+
if (!value || typeof value !== "object") {
|
|
230
|
+
return new Set(["unknown"]);
|
|
231
|
+
}
|
|
232
|
+
if (value.kind === "union" && Array.isArray(value.options)) {
|
|
233
|
+
const kinds = new Set();
|
|
234
|
+
value.options.forEach((option) => {
|
|
235
|
+
flattenKinds(option).forEach((kind) => kinds.add(kind));
|
|
236
|
+
});
|
|
237
|
+
return kinds.size > 0 ? kinds : new Set(["unknown"]);
|
|
238
|
+
}
|
|
239
|
+
return new Set([normalizePrimitiveType(value.kind)]);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const leftKinds = flattenKinds(expected);
|
|
243
|
+
const rightKinds = flattenKinds(actual);
|
|
244
|
+
|
|
245
|
+
if (leftKinds.has("any") || rightKinds.has("any")) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (leftKinds.has("unknown") || rightKinds.has("unknown")) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const actualKind of rightKinds) {
|
|
254
|
+
if (!leftKinds.has(actualKind)) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return true;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const compareTypeSpecificity = (left = "unknown", right = "unknown") => {
|
|
263
|
+
const toComparableKind = (value) => {
|
|
264
|
+
if (typeof value === "string") {
|
|
265
|
+
return normalizePrimitiveType(value);
|
|
266
|
+
}
|
|
267
|
+
if (!value || typeof value !== "object") {
|
|
268
|
+
return "unknown";
|
|
269
|
+
}
|
|
270
|
+
if (value.kind === "union" && Array.isArray(value.options) && value.options.length > 0) {
|
|
271
|
+
const firstOption = value.options[0];
|
|
272
|
+
return normalizePrimitiveType(firstOption?.kind || "unknown");
|
|
273
|
+
}
|
|
274
|
+
return normalizePrimitiveType(value.kind || "unknown");
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const leftIndex = PRIMITIVE_TYPE_ORDER.indexOf(toComparableKind(left));
|
|
278
|
+
const rightIndex = PRIMITIVE_TYPE_ORDER.indexOf(toComparableKind(right));
|
|
279
|
+
|
|
280
|
+
return leftIndex - rightIndex;
|
|
281
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const toKebabCase = (value = "") => {
|
|
2
|
+
return String(value)
|
|
3
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
4
|
+
.toLowerCase();
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const toCamelCase = (value = "") => {
|
|
8
|
+
return String(value).replace(/-([a-z0-9])/g, (_, chr) => chr.toUpperCase());
|
|
9
|
+
};
|
package/src/utils/fs.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const isDirectoryPath = (targetPath) => {
|
|
5
|
+
try {
|
|
6
|
+
return statSync(targetPath).isDirectory();
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const walkFiles = (dirPaths = [], output = []) => {
|
|
13
|
+
dirPaths.forEach((dirPath) => {
|
|
14
|
+
if (!isDirectoryPath(dirPath)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const entries = readdirSync(dirPath);
|
|
19
|
+
entries.forEach((entry) => {
|
|
20
|
+
const fullPath = path.join(dirPath, entry);
|
|
21
|
+
const stats = statSync(fullPath);
|
|
22
|
+
if (stats.isDirectory()) {
|
|
23
|
+
walkFiles([fullPath], output);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
output.push(fullPath);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
return output;
|
|
30
|
+
};
|