jsxpression 0.1.41 → 0.2.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/lib/analyze/analysis-error.d.ts.map +1 -1
- package/lib/analyze/analysis-error.js +2 -7
- package/lib/analyze/analysis-error.js.map +1 -1
- package/lib/analyze/analysis-report.d.ts +13 -13
- package/lib/analyze/analysis-report.d.ts.map +1 -1
- package/lib/analyze/analysis-report.js +48 -45
- package/lib/analyze/analysis-report.js.map +1 -1
- package/lib/analyze/analyze.d.ts +1 -1
- package/lib/analyze/analyze.d.ts.map +1 -1
- package/lib/analyze/analyze.js +4 -4
- package/lib/analyze/analyze.js.map +1 -1
- package/lib/analyze/data/analyze.d.ts +1 -1
- package/lib/analyze/data/analyze.d.ts.map +1 -1
- package/lib/analyze/data/analyze.js +5 -5
- package/lib/analyze/data/analyze.js.map +1 -1
- package/lib/analyze/data/data-access.d.ts +1 -1
- package/lib/analyze/data/data-access.d.ts.map +1 -1
- package/lib/analyze/data/data-access.js +19 -15
- package/lib/analyze/data/data-access.js.map +1 -1
- package/lib/analyze/data/method-calls.d.ts +1 -1
- package/lib/analyze/data/method-calls.d.ts.map +1 -1
- package/lib/analyze/data/method-calls.js +5 -5
- package/lib/analyze/data/method-calls.js.map +1 -1
- package/lib/analyze/data/utils.d.ts +3 -3
- package/lib/analyze/data/utils.d.ts.map +1 -1
- package/lib/analyze/data/utils.js +6 -5
- package/lib/analyze/data/utils.js.map +1 -1
- package/lib/analyze/index.d.ts +1 -1
- package/lib/analyze/index.d.ts.map +1 -1
- package/lib/analyze/index.js +1 -1
- package/lib/analyze/index.js.map +1 -1
- package/lib/analyze/jsx/analyze.d.ts +1 -1
- package/lib/analyze/jsx/analyze.d.ts.map +1 -1
- package/lib/analyze/jsx/analyze.js +4 -4
- package/lib/analyze/jsx/analyze.js.map +1 -1
- package/lib/analyze/jsx/element-attributes.d.ts +1 -1
- package/lib/analyze/jsx/element-attributes.d.ts.map +1 -1
- package/lib/analyze/jsx/element-attributes.js +3 -3
- package/lib/analyze/jsx/element-attributes.js.map +1 -1
- package/lib/analyze/jsx/element-children.d.ts +1 -1
- package/lib/analyze/jsx/element-children.d.ts.map +1 -1
- package/lib/analyze/jsx/element-children.js +3 -3
- package/lib/analyze/jsx/element-children.js.map +1 -1
- package/lib/analyze/jsx/element-tags.d.ts +1 -1
- package/lib/analyze/jsx/element-tags.d.ts.map +1 -1
- package/lib/analyze/jsx/element-tags.js +2 -2
- package/lib/analyze/jsx/element-tags.js.map +1 -1
- package/lib/analyze/security/analyze.d.ts +1 -1
- package/lib/analyze/security/analyze.d.ts.map +1 -1
- package/lib/analyze/security/analyze.js +4 -4
- package/lib/analyze/security/analyze.js.map +1 -1
- package/lib/analyze/security/call-expressions.d.ts +1 -1
- package/lib/analyze/security/call-expressions.d.ts.map +1 -1
- package/lib/analyze/security/call-expressions.js +3 -5
- package/lib/analyze/security/call-expressions.js.map +1 -1
- package/lib/analyze/security/declarations.d.ts +1 -1
- package/lib/analyze/security/declarations.d.ts.map +1 -1
- package/lib/analyze/security/declarations.js +6 -6
- package/lib/analyze/security/declarations.js.map +1 -1
- package/lib/analyze/security/expressions.d.ts +1 -1
- package/lib/analyze/security/expressions.d.ts.map +1 -1
- package/lib/analyze/security/expressions.js +3 -6
- package/lib/analyze/security/expressions.js.map +1 -1
- package/lib/analyze/security/identifiers.d.ts +1 -1
- package/lib/analyze/security/identifiers.d.ts.map +1 -1
- package/lib/analyze/security/identifiers.js +19 -22
- package/lib/analyze/security/identifiers.js.map +1 -1
- package/lib/analyze/utils.d.ts +4 -4
- package/lib/analyze/utils.d.ts.map +1 -1
- package/lib/analyze/utils.js +5 -3
- package/lib/analyze/utils.js.map +1 -1
- package/lib/analyze/validation-context.d.ts +8 -8
- package/lib/analyze/validation-context.d.ts.map +1 -1
- package/lib/analyze/validation-context.js +23 -21
- package/lib/analyze/validation-context.js.map +1 -1
- package/lib/builtins.d.ts +6 -6
- package/lib/builtins.d.ts.map +1 -1
- package/lib/builtins.js +9 -8
- package/lib/builtins.js.map +1 -1
- package/lib/codegen/builtins/builtin-types.d.ts.map +1 -1
- package/lib/codegen/builtins/builtin-types.js +9 -13
- package/lib/codegen/builtins/builtin-types.js.map +1 -1
- package/lib/codegen/generate-typescript-definitions.d.ts +18 -17
- package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -1
- package/lib/codegen/generate-typescript-definitions.js +19 -23
- package/lib/codegen/generate-typescript-definitions.js.map +1 -1
- package/lib/codegen/schema/component-types.d.ts.map +1 -1
- package/lib/codegen/schema/component-types.js +7 -4
- package/lib/codegen/schema/component-types.js.map +1 -1
- package/lib/codegen/schema/data-types.d.ts.map +1 -1
- package/lib/codegen/schema/data-types.js +1 -1
- package/lib/codegen/schema/data-types.js.map +1 -1
- package/lib/codegen/schema/function-types.d.ts.map +1 -1
- package/lib/codegen/schema/function-types.js +1 -3
- package/lib/codegen/schema/function-types.js.map +1 -1
- package/lib/codegen/schema/utils.d.ts +4 -3
- package/lib/codegen/schema/utils.d.ts.map +1 -1
- package/lib/codegen/schema/utils.js +12 -8
- package/lib/codegen/schema/utils.js.map +1 -1
- package/lib/compile/compilation-error.d.ts +1 -1
- package/lib/compile/compilation-error.d.ts.map +1 -1
- package/lib/compile/compilation-error.js +3 -1
- package/lib/compile/compilation-error.js.map +1 -1
- package/lib/compile/compile.d.ts.map +1 -1
- package/lib/compile/compile.js +5 -11
- package/lib/compile/compile.js.map +1 -1
- package/lib/evaluate/evaluate.d.ts +6 -6
- package/lib/evaluate/evaluate.d.ts.map +1 -1
- package/lib/evaluate/evaluate.js +1 -1
- package/lib/evaluate/evaluate.js.map +1 -1
- package/lib/evaluate/evaluation-error.d.ts.map +1 -1
- package/lib/evaluate/evaluation-error.js +1 -0
- package/lib/evaluate/evaluation-error.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/jsx.d.ts +3 -3
- package/lib/jsx.d.ts.map +1 -1
- package/lib/jsx.js.map +1 -1
- package/lib/parse/parse-error.d.ts.map +1 -1
- package/lib/parse/parse-error.js +1 -0
- package/lib/parse/parse-error.js.map +1 -1
- package/lib/render.d.ts +25 -33
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +21 -26
- package/lib/render.js.map +1 -1
- package/lib/schema.d.ts +9 -9
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +1 -1
- package/lib/schema.js.map +1 -1
- package/lib/traverse.d.ts.map +1 -1
- package/lib/traverse.js.map +1 -1
- package/lib/validate.d.ts +14 -17
- package/lib/validate.d.ts.map +1 -1
- package/lib/validate.js +13 -14
- package/lib/validate.js.map +1 -1
- package/package.json +12 -12
- package/src/analyze/analysis-error.ts +2 -6
- package/src/analyze/analysis-report.ts +73 -62
- package/src/analyze/analyze.test.ts +60 -60
- package/src/analyze/analyze.ts +4 -4
- package/src/analyze/data/analyze.ts +5 -8
- package/src/analyze/data/data-access.ts +49 -46
- package/src/analyze/data/method-calls.ts +9 -18
- package/src/analyze/data/utils.ts +15 -19
- package/src/analyze/index.ts +1 -1
- package/src/analyze/jsx/analyze.ts +4 -4
- package/src/analyze/jsx/element-attributes.ts +5 -18
- package/src/analyze/jsx/element-children.ts +4 -8
- package/src/analyze/jsx/element-tags.ts +2 -2
- package/src/analyze/security/analyze.ts +4 -4
- package/src/analyze/security/call-expressions.ts +5 -18
- package/src/analyze/security/declarations.ts +14 -43
- package/src/analyze/security/expressions.ts +12 -49
- package/src/analyze/security/identifiers.ts +37 -39
- package/src/analyze/utils.ts +25 -19
- package/src/analyze/validation-context.ts +32 -23
- package/src/builtins.ts +27 -38
- package/src/codegen/builtins/builtin-types.ts +13 -20
- package/src/codegen/generate-typescript-definitions.test.ts +6 -6
- package/src/codegen/generate-typescript-definitions.ts +19 -23
- package/src/codegen/schema/component-types.ts +22 -12
- package/src/codegen/schema/data-types.ts +6 -6
- package/src/codegen/schema/function-types.ts +1 -3
- package/src/codegen/schema/utils.ts +24 -23
- package/src/compile/compilation-error.ts +5 -1
- package/src/compile/compile.test.ts +7 -21
- package/src/compile/compile.ts +18 -30
- package/src/evaluate/evaluate.test.ts +78 -94
- package/src/evaluate/evaluate.ts +15 -20
- package/src/evaluate/evaluation-error.ts +1 -0
- package/src/index.ts +1 -7
- package/src/jsx.ts +4 -6
- package/src/parse/parse-error.ts +1 -0
- package/src/parse/parse.test.ts +16 -18
- package/src/render.test.ts +4 -4
- package/src/render.ts +26 -38
- package/src/schema.ts +11 -11
- package/src/traverse.test.ts +6 -6
- package/src/traverse.ts +3 -3
- package/src/validate.ts +14 -17
- package/CHANGELOG.md +0 -8
|
@@ -169,9 +169,7 @@ describe("analyze - consolidated tests", () => {
|
|
|
169
169
|
const ast = parse("<Text>{items[variable]}</Text>");
|
|
170
170
|
const result = analyze(ast, baseSchema);
|
|
171
171
|
expect(result.hasErrors).toBe(true);
|
|
172
|
-
expect(result.errors.some((e) => e.message.includes("variable") || e.message.includes("not allowed"))).toBe(
|
|
173
|
-
true
|
|
174
|
-
);
|
|
172
|
+
expect(result.errors.some((e) => e.message.includes("variable") || e.message.includes("not allowed"))).toBe(true);
|
|
175
173
|
});
|
|
176
174
|
|
|
177
175
|
it("should block string-based computed access on objects", () => {
|
|
@@ -230,17 +228,13 @@ describe("analyze - consolidated tests", () => {
|
|
|
230
228
|
});
|
|
231
229
|
|
|
232
230
|
it("should handle complex nested scenarios", () => {
|
|
233
|
-
const ast = parse(
|
|
234
|
-
"<Text>{departments.map((dept) => dept.employees.map((employee) => employee.salary))}</Text>"
|
|
235
|
-
);
|
|
231
|
+
const ast = parse("<Text>{departments.map((dept) => dept.employees.map((employee) => employee.salary))}</Text>");
|
|
236
232
|
const result = analyze(ast, baseSchema);
|
|
237
233
|
expect(result.hasErrors).toBe(false);
|
|
238
234
|
});
|
|
239
235
|
|
|
240
236
|
it("should detect typos in complex nested scenarios", () => {
|
|
241
|
-
const ast = parse(
|
|
242
|
-
"<Text>{departments.map((dept) => dept.employees.map((employee) => employee.saladry))}</Text>"
|
|
243
|
-
);
|
|
237
|
+
const ast = parse("<Text>{departments.map((dept) => dept.employees.map((employee) => employee.saladry))}</Text>");
|
|
244
238
|
const result = analyze(ast, baseSchema);
|
|
245
239
|
expect(result.hasErrors).toBe(true);
|
|
246
240
|
expect(result.errors[0].message).toContain("Property 'saladry' does not exist on parameter 'employee'");
|
|
@@ -256,14 +250,16 @@ describe("analyze - consolidated tests", () => {
|
|
|
256
250
|
|
|
257
251
|
// Test each builtin object (Math, Array.prototype, String.prototype)
|
|
258
252
|
Object.entries(builtins).forEach(([objectName, schema]) => {
|
|
259
|
-
if (!schema.methods)
|
|
253
|
+
if (!schema.methods) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
260
256
|
|
|
261
257
|
describe(`${objectName} methods`, () => {
|
|
262
258
|
Object.entries(schema.methods!).forEach(([methodName, methodDef]) => {
|
|
263
259
|
const displayName = objectName === "Math" ? `Math.${methodName}` : methodName;
|
|
264
260
|
|
|
265
261
|
it(`should allow ${displayName} with valid parameters`, () => {
|
|
266
|
-
let testExpression: string;
|
|
262
|
+
let testExpression: string = "";
|
|
267
263
|
|
|
268
264
|
if (objectName === "Math") {
|
|
269
265
|
// Math methods
|
|
@@ -325,60 +321,62 @@ describe("analyze - consolidated tests", () => {
|
|
|
325
321
|
const requiredParams = methodDef.params.filter((p) => p.required);
|
|
326
322
|
const nonVariadicParams = methodDef.params.filter((p) => !p.variadic);
|
|
327
323
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
324
|
+
it(`should validate ${displayName} minimum parameters`, () => {
|
|
325
|
+
if (requiredParams.length === 0) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
331
328
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
329
|
+
let testExpression: string = "";
|
|
330
|
+
|
|
331
|
+
if (objectName === "Math") {
|
|
332
|
+
testExpression = `<Text>{Math.${methodName}()}</Text>`;
|
|
333
|
+
} else if (objectName === "Array.prototype") {
|
|
334
|
+
testExpression = `<Text>{items.${methodName}()}</Text>`;
|
|
335
|
+
} else if (objectName === "String.prototype") {
|
|
336
|
+
testExpression = `<Text>{text.${methodName}()}</Text>`;
|
|
337
|
+
} else {
|
|
338
|
+
testExpression = `<Text>{${objectName}.${methodName}()}</Text>`;
|
|
339
|
+
}
|
|
341
340
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
);
|
|
348
|
-
});
|
|
349
|
-
}
|
|
341
|
+
const ast = parse(testExpression);
|
|
342
|
+
const result = analyze(ast, baseSchema);
|
|
343
|
+
expect(result.hasWarnings).toBe(true);
|
|
344
|
+
expect(result.warnings[0].message).toContain(`${displayName} expects at least ${requiredParams.length} parameter`);
|
|
345
|
+
});
|
|
350
346
|
|
|
351
347
|
// Test maximum parameters for non-variadic methods
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
} else if (objectName === "String.prototype") {
|
|
371
|
-
testExpression = `<Text>{text.${methodName}(${extraParams})}</Text>`;
|
|
348
|
+
it(`should validate ${displayName} maximum parameters`, () => {
|
|
349
|
+
if (!(nonVariadicParams.length === methodDef.params.length && methodDef.params.length > 0)) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Create a call with too many parameters
|
|
354
|
+
const maxParams = methodDef.params.length;
|
|
355
|
+
const extraParams = Array(maxParams + 2)
|
|
356
|
+
.fill("1")
|
|
357
|
+
.join(", ");
|
|
358
|
+
|
|
359
|
+
let testExpression: string = "";
|
|
360
|
+
|
|
361
|
+
if (objectName === "Math") {
|
|
362
|
+
testExpression = `<Text>{Math.${methodName}(${extraParams})}</Text>`;
|
|
363
|
+
} else if (objectName === "Array.prototype") {
|
|
364
|
+
if (methodName === "map" || methodName === "filter") {
|
|
365
|
+
testExpression = `<Text>{items.${methodName}(x => x, {}, ${extraParams})}</Text>`;
|
|
372
366
|
} else {
|
|
373
|
-
testExpression = `<Text>{
|
|
367
|
+
testExpression = `<Text>{items.${methodName}(${extraParams})}</Text>`;
|
|
374
368
|
}
|
|
369
|
+
} else if (objectName === "String.prototype") {
|
|
370
|
+
testExpression = `<Text>{text.${methodName}(${extraParams})}</Text>`;
|
|
371
|
+
} else {
|
|
372
|
+
testExpression = `<Text>{${objectName}.${methodName}(${extraParams})}</Text>`;
|
|
373
|
+
}
|
|
375
374
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
375
|
+
const ast = parse(testExpression);
|
|
376
|
+
const result = analyze(ast, baseSchema);
|
|
377
|
+
expect(result.hasWarnings).toBe(true);
|
|
378
|
+
expect(result.warnings[0].message).toContain(`${displayName} expects at most ${maxParams} parameter`);
|
|
379
|
+
});
|
|
382
380
|
});
|
|
383
381
|
});
|
|
384
382
|
});
|
|
@@ -388,12 +386,14 @@ describe("analyze - consolidated tests", () => {
|
|
|
388
386
|
const builtins = getBuiltins();
|
|
389
387
|
|
|
390
388
|
Object.entries(builtins).forEach(([objectName, schema]) => {
|
|
391
|
-
if (!schema.properties)
|
|
389
|
+
if (!schema.properties) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
392
|
|
|
393
393
|
describe(`${objectName} properties`, () => {
|
|
394
394
|
Object.entries(schema.properties!).forEach(([propName, propDef]) => {
|
|
395
395
|
it(`should allow ${objectName}.${propName} property access`, () => {
|
|
396
|
-
let testExpression: string;
|
|
396
|
+
let testExpression: string = "";
|
|
397
397
|
|
|
398
398
|
if (objectName === "Array.prototype") {
|
|
399
399
|
testExpression = `<Text>{items.${propName}}</Text>`;
|
package/src/analyze/analyze.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Program } from "acorn";
|
|
2
2
|
import type { Schema } from "../schema.js";
|
|
3
|
-
import { AnalysisReport } from "./analysis-report.js";
|
|
4
|
-
import { analyzeSecurity } from "./security/index.js";
|
|
5
|
-
import { analyzeJsx } from "./jsx/index.js";
|
|
3
|
+
import { createAnalysisReport, type AnalysisReport } from "./analysis-report.js";
|
|
6
4
|
import { analyzeData } from "./data/index.js";
|
|
5
|
+
import { analyzeJsx } from "./jsx/index.js";
|
|
6
|
+
import { analyzeSecurity } from "./security/index.js";
|
|
7
7
|
|
|
8
8
|
export function analyze(ast: Program, schema: Schema): AnalysisReport {
|
|
9
|
-
return
|
|
9
|
+
return createAnalysisReport().merge(analyzeSecurity(ast, schema), analyzeJsx(ast, schema), analyzeData(ast, schema));
|
|
10
10
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import type { Program } from "acorn";
|
|
2
2
|
import type { Schema } from "../../schema.js";
|
|
3
|
-
import { AnalysisReport } from "../analysis-report.js";
|
|
4
|
-
import {
|
|
5
|
-
import { analyzeMethodCalls } from "./method-calls.js";
|
|
3
|
+
import { createAnalysisReport, type AnalysisReport } from "../analysis-report.js";
|
|
4
|
+
import { createValidationContext } from "../validation-context.js";
|
|
6
5
|
import { analyzeDataAccess } from "./data-access.js";
|
|
6
|
+
import { analyzeMethodCalls } from "./method-calls.js";
|
|
7
7
|
|
|
8
8
|
export function analyzeData(ast: Program, schema: Schema): AnalysisReport {
|
|
9
|
-
const validationContext =
|
|
9
|
+
const validationContext = createValidationContext();
|
|
10
10
|
|
|
11
|
-
return
|
|
12
|
-
analyzeMethodCalls(ast, schema, validationContext),
|
|
13
|
-
analyzeDataAccess(ast, schema, validationContext)
|
|
14
|
-
);
|
|
11
|
+
return createAnalysisReport().merge(analyzeMethodCalls(ast, schema, validationContext), analyzeDataAccess(ast, schema, validationContext));
|
|
15
12
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import type { Program } from "acorn";
|
|
2
|
-
import { traverse } from "../../traverse.js";
|
|
1
|
+
import type { AnyNode, ArrowFunctionExpression, CallExpression, Identifier, MemberExpression, Program } from "acorn";
|
|
3
2
|
import type { ArrayPropertySchema, PropertySchema, Schema } from "../../schema.js";
|
|
3
|
+
import { traverse } from "../../traverse.js";
|
|
4
|
+
import { createAnalysisReport, type AnalysisReport } from "../analysis-report.js";
|
|
5
|
+
import { getNodeRange } from "../utils.js";
|
|
4
6
|
import type { ValidationContext } from "../validation-context.js";
|
|
5
|
-
import { AnalysisReport } from "../analysis-report.js";
|
|
6
7
|
import { isSimpleDataAccess, extractPath, validateSchemaPath } from "./utils.js";
|
|
7
|
-
import { getNodeRange } from "../utils.js";
|
|
8
8
|
|
|
9
9
|
export function analyzeDataAccess(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
|
|
10
|
-
const analysisReport =
|
|
10
|
+
const analysisReport = createAnalysisReport();
|
|
11
11
|
|
|
12
12
|
const dataKeys = new Set(Object.keys(schema.data ?? {}));
|
|
13
13
|
const dataPaths = new Set<string>();
|
|
14
|
-
const arrowFunctionContexts = new Map<
|
|
14
|
+
const arrowFunctionContexts = new Map<ArrowFunctionExpression, Map<string, PropertySchema>>();
|
|
15
15
|
|
|
16
16
|
// First pass: collect all data paths and arrow function contexts (including nested ones)
|
|
17
|
-
function collectArrowFunctionContexts(node:
|
|
17
|
+
function collectArrowFunctionContexts(node: AnyNode): void {
|
|
18
18
|
if (node.type === "CallExpression") {
|
|
19
19
|
const { callee, arguments: args } = node;
|
|
20
20
|
|
|
@@ -35,7 +35,7 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
|
|
|
35
35
|
const arrowFunction = args[0];
|
|
36
36
|
|
|
37
37
|
if (arrayElementType && arrowFunction.params.length > 0) {
|
|
38
|
-
const parameterTypes = new Map();
|
|
38
|
+
const parameterTypes = new Map<string, PropertySchema>();
|
|
39
39
|
|
|
40
40
|
// First parameter is always the array element
|
|
41
41
|
if (arrowFunction.params[0].type === "Identifier") {
|
|
@@ -54,17 +54,18 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Recursively traverse all child nodes
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
const record = node as unknown as Record<string, unknown>;
|
|
58
|
+
for (const key of Object.keys(record)) {
|
|
59
|
+
const value = record[key];
|
|
59
60
|
if (value && typeof value === "object") {
|
|
60
61
|
if (Array.isArray(value)) {
|
|
61
62
|
for (const child of value) {
|
|
62
|
-
if (child && typeof child === "object" && child
|
|
63
|
-
collectArrowFunctionContexts(child);
|
|
63
|
+
if (child && typeof child === "object" && (child as Record<string, unknown>)["type"]) {
|
|
64
|
+
collectArrowFunctionContexts(child as AnyNode);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
|
-
} else if (value
|
|
67
|
-
collectArrowFunctionContexts(value);
|
|
67
|
+
} else if ((value as Record<string, unknown>)["type"]) {
|
|
68
|
+
collectArrowFunctionContexts(value as AnyNode);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -105,7 +106,7 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
|
|
|
105
106
|
traverse(ast, {
|
|
106
107
|
MemberExpression(node) {
|
|
107
108
|
const parent = parentMap.get(node);
|
|
108
|
-
if (parent
|
|
109
|
+
if (parent?.type === "CallExpression" && parent.callee === node) {
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -138,10 +139,10 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
function findEnclosingArrowContext(
|
|
141
|
-
targetNode:
|
|
142
|
-
arrowFunctionContexts: Map<
|
|
143
|
-
): Map<string,
|
|
144
|
-
let best: { arrowFn:
|
|
142
|
+
targetNode: MemberExpression,
|
|
143
|
+
arrowFunctionContexts: Map<ArrowFunctionExpression, Map<string, PropertySchema>>
|
|
144
|
+
): Map<string, PropertySchema> | null {
|
|
145
|
+
let best: { arrowFn: ArrowFunctionExpression; size: number } | null = null;
|
|
145
146
|
for (const arrowFn of arrowFunctionContexts.keys()) {
|
|
146
147
|
if (targetNode.start >= arrowFn.start && targetNode.end <= arrowFn.end) {
|
|
147
148
|
const size = arrowFn.end - arrowFn.start;
|
|
@@ -150,14 +151,14 @@ function findEnclosingArrowContext(
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
|
-
return best ? arrowFunctionContexts.get(best.arrowFn)
|
|
154
|
+
return best ? (arrowFunctionContexts.get(best.arrowFn) ?? null) : null;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
function getArrayElementTypeFromCall(
|
|
157
|
-
node:
|
|
158
|
+
node: CallExpression,
|
|
158
159
|
schema: Schema,
|
|
159
|
-
arrowFunctionContexts: Map<
|
|
160
|
-
):
|
|
160
|
+
arrowFunctionContexts: Map<ArrowFunctionExpression, Map<string, PropertySchema>>
|
|
161
|
+
): PropertySchema | null {
|
|
161
162
|
const { callee } = node;
|
|
162
163
|
const dataKeys = new Set(Object.keys(schema.data ?? {}));
|
|
163
164
|
|
|
@@ -188,9 +189,9 @@ function getArrayElementTypeFromCall(
|
|
|
188
189
|
// Find the arrow function context that defines this parameter
|
|
189
190
|
const parameterType = resolveParameterType(paramName, node, arrowFunctionContexts);
|
|
190
191
|
|
|
191
|
-
if (parameterType
|
|
192
|
+
if (parameterType?.type === "object" && parameterType.shape) {
|
|
192
193
|
const propertySchema = parameterType.shape[propertyName];
|
|
193
|
-
if (propertySchema
|
|
194
|
+
if (propertySchema?.type === "array") {
|
|
194
195
|
return getArrayElementType(propertySchema);
|
|
195
196
|
}
|
|
196
197
|
}
|
|
@@ -202,9 +203,9 @@ function getArrayElementTypeFromCall(
|
|
|
202
203
|
|
|
203
204
|
function resolveParameterType(
|
|
204
205
|
paramName: string,
|
|
205
|
-
currentNode:
|
|
206
|
-
arrowFunctionContexts: Map<
|
|
207
|
-
):
|
|
206
|
+
currentNode: AnyNode,
|
|
207
|
+
arrowFunctionContexts: Map<ArrowFunctionExpression, Map<string, PropertySchema>>
|
|
208
|
+
): PropertySchema | undefined | null {
|
|
208
209
|
// Find the arrow function that contains this node and has the parameter
|
|
209
210
|
for (const [arrowFunction, parameterTypes] of arrowFunctionContexts.entries()) {
|
|
210
211
|
if (parameterTypes.has(paramName)) {
|
|
@@ -217,7 +218,7 @@ function resolveParameterType(
|
|
|
217
218
|
return null;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
function isNodeInsideArrowFunction(targetNode:
|
|
221
|
+
function isNodeInsideArrowFunction(targetNode: AnyNode, arrowFunction: ArrowFunctionExpression): boolean {
|
|
221
222
|
// Simple check: if the target node's position is within the arrow function's range
|
|
222
223
|
if (targetNode.start >= arrowFunction.start && targetNode.end <= arrowFunction.end) {
|
|
223
224
|
return true;
|
|
@@ -225,9 +226,10 @@ function isNodeInsideArrowFunction(targetNode: any, arrowFunction: any): boolean
|
|
|
225
226
|
return false;
|
|
226
227
|
}
|
|
227
228
|
|
|
228
|
-
function getSchemaAtPath(path: string
|
|
229
|
+
function getSchemaAtPath(path: Array<string>, schema: Schema): PropertySchema | null | undefined {
|
|
229
230
|
if (!schema.data || path.length === 0) {
|
|
230
|
-
|
|
231
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- this branch returns the raw schema.data record (not a PropertySchema) when no path segments remain; preserved as-is for behavior parity
|
|
232
|
+
return schema.data as any;
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
let current = schema.data[path[0]];
|
|
@@ -244,8 +246,8 @@ function getSchemaAtPath(path: string[], schema: Schema): any {
|
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
function validateParameterAccess(
|
|
247
|
-
node:
|
|
248
|
-
paramType:
|
|
249
|
+
node: MemberExpression,
|
|
250
|
+
paramType: PropertySchema,
|
|
249
251
|
analysisReport: AnalysisReport,
|
|
250
252
|
validationContext: ValidationContext
|
|
251
253
|
): void {
|
|
@@ -259,7 +261,7 @@ function validateParameterAccess(
|
|
|
259
261
|
if (!paramType.shape[propertyName]) {
|
|
260
262
|
analysisReport.addIssue(
|
|
261
263
|
"INVALID_PARAMETER_ACCESS",
|
|
262
|
-
`Property '${propertyName}' does not exist on parameter '${node.object.name}'`,
|
|
264
|
+
`Property '${propertyName}' does not exist on parameter '${(node.object as Identifier).name}'`,
|
|
263
265
|
getNodeRange(node),
|
|
264
266
|
validationContext.getSnapshot(),
|
|
265
267
|
Object.keys(paramType.shape)
|
|
@@ -268,9 +270,7 @@ function validateParameterAccess(
|
|
|
268
270
|
} else {
|
|
269
271
|
analysisReport.addIssue(
|
|
270
272
|
"INVALID_PARAMETER_ACCESS",
|
|
271
|
-
`Cannot access property '${propertyName}' on ${paramType.type
|
|
272
|
-
node.object.name
|
|
273
|
-
}'`,
|
|
273
|
+
`Cannot access property '${propertyName}' on ${paramType.type ?? "undefined"} type parameter '${(node.object as Identifier).name}'`,
|
|
274
274
|
getNodeRange(node),
|
|
275
275
|
validationContext.getSnapshot()
|
|
276
276
|
);
|
|
@@ -281,20 +281,23 @@ export function getArrayElementType(schema: ArrayPropertySchema): PropertySchema
|
|
|
281
281
|
return schema.shape || null;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
-
function buildParentMap(ast: Program): Map<
|
|
285
|
-
const map = new Map<
|
|
286
|
-
function visit(node:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
284
|
+
function buildParentMap(ast: Program): Map<AnyNode, AnyNode | null> {
|
|
285
|
+
const map = new Map<AnyNode, AnyNode | null>();
|
|
286
|
+
function visit(node: unknown, parent: AnyNode | null): void {
|
|
287
|
+
const record = node as Record<string, unknown> | null;
|
|
288
|
+
if (!record || typeof record !== "object" || !record["type"]) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
map.set(node as AnyNode, parent);
|
|
292
|
+
for (const key of Object.keys(record)) {
|
|
293
|
+
const value = record[key];
|
|
291
294
|
if (value && typeof value === "object") {
|
|
292
295
|
if (Array.isArray(value)) {
|
|
293
296
|
for (const child of value) {
|
|
294
|
-
visit(child, node);
|
|
297
|
+
visit(child, node as AnyNode);
|
|
295
298
|
}
|
|
296
299
|
} else {
|
|
297
|
-
visit(value, node);
|
|
300
|
+
visit(value, node as AnyNode);
|
|
298
301
|
}
|
|
299
302
|
}
|
|
300
303
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { Program } from "acorn";
|
|
2
|
-
import { traverse } from "../../traverse.js";
|
|
3
|
-
import type { Schema } from "../../schema.js";
|
|
4
2
|
import { isMethodAllowed, getMethodDefinition, getMethodDisplayName } from "../../builtins.js";
|
|
3
|
+
import type { Schema } from "../../schema.js";
|
|
4
|
+
import { traverse } from "../../traverse.js";
|
|
5
|
+
import { createAnalysisReport, type AnalysisReport } from "../analysis-report.js";
|
|
6
|
+
import { getNodeRange } from "../utils.js";
|
|
5
7
|
import type { ValidationContext } from "../validation-context.js";
|
|
6
|
-
import { AnalysisReport } from "../analysis-report.js";
|
|
7
8
|
import { isSimpleDataAccess, extractPath, validateSchemaPath } from "./utils.js";
|
|
8
|
-
import { getNodeRange } from "../utils.js";
|
|
9
9
|
|
|
10
10
|
export function analyzeMethodCalls(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
|
|
11
|
-
const analysisReport =
|
|
11
|
+
const analysisReport = createAnalysisReport();
|
|
12
12
|
const dataKeys = new Set(Object.keys(schema.data ?? {}));
|
|
13
13
|
|
|
14
14
|
traverse(ast, {
|
|
@@ -31,18 +31,13 @@ export function analyzeMethodCalls(ast: Program, schema: Schema, validationConte
|
|
|
31
31
|
if (!isMethodAllowed(method)) {
|
|
32
32
|
const methodContext = callee.object.type === "Identifier" ? `${callee.object.name}.${method}` : `.${method}`;
|
|
33
33
|
|
|
34
|
-
analysisReport.addIssue(
|
|
35
|
-
"METHOD_NOT_ALLOWED",
|
|
36
|
-
`method ${methodContext} not allowed`,
|
|
37
|
-
getNodeRange(callee),
|
|
38
|
-
validationContext.getSnapshot()
|
|
39
|
-
);
|
|
34
|
+
analysisReport.addIssue("METHOD_NOT_ALLOWED", `method ${methodContext} not allowed`, getNodeRange(callee), validationContext.getSnapshot());
|
|
40
35
|
return;
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
const methodDef = getMethodDefinition(method);
|
|
44
39
|
|
|
45
|
-
if (!methodDef
|
|
40
|
+
if (!methodDef?.params) {
|
|
46
41
|
return;
|
|
47
42
|
}
|
|
48
43
|
|
|
@@ -70,9 +65,7 @@ export function analyzeMethodCalls(ast: Program, schema: Schema, validationConte
|
|
|
70
65
|
if (args.length < minParams) {
|
|
71
66
|
analysisReport.addIssue(
|
|
72
67
|
"INSUFFICIENT_PARAMETERS",
|
|
73
|
-
`${getMethodDisplayName(method)} expects at least ${minParams} parameter${
|
|
74
|
-
minParams !== 1 ? "s" : ""
|
|
75
|
-
}, got ${args.length}`,
|
|
68
|
+
`${getMethodDisplayName(method)} expects at least ${minParams} parameter${minParams !== 1 ? "s" : ""}, got ${args.length}`,
|
|
76
69
|
getNodeRange(node),
|
|
77
70
|
validationContext.getSnapshot()
|
|
78
71
|
);
|
|
@@ -81,9 +74,7 @@ export function analyzeMethodCalls(ast: Program, schema: Schema, validationConte
|
|
|
81
74
|
if (args.length > maxParams) {
|
|
82
75
|
analysisReport.addIssue(
|
|
83
76
|
"TOO_MANY_PARAMETERS",
|
|
84
|
-
`${getMethodDisplayName(method)} expects at most ${maxParams} parameter${
|
|
85
|
-
maxParams !== 1 ? "s" : ""
|
|
86
|
-
}, got ${args.length}`,
|
|
77
|
+
`${getMethodDisplayName(method)} expects at most ${maxParams} parameter${maxParams !== 1 ? "s" : ""}, got ${args.length}`,
|
|
87
78
|
getNodeRange(node),
|
|
88
79
|
validationContext.getSnapshot()
|
|
89
80
|
);
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import type { MemberExpression } from "acorn";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
isAllowedOnString,
|
|
5
|
-
getAvailableArrayMembers,
|
|
6
|
-
getAvailableStringMembers,
|
|
7
|
-
} from "../../builtins.js";
|
|
8
|
-
import type { Schema } from "../../schema.js";
|
|
1
|
+
import type { Expression, MemberExpression, PrivateIdentifier, Super } from "acorn";
|
|
2
|
+
import { isAllowedOnArray, isAllowedOnString, getAvailableArrayMembers, getAvailableStringMembers } from "../../builtins.js";
|
|
3
|
+
import type { PropertySchema, Schema } from "../../schema.js";
|
|
9
4
|
import type { AnalysisReport } from "../analysis-report.js";
|
|
10
5
|
import { getNodeRange } from "../utils.js";
|
|
11
6
|
import type { ValidationContext } from "../validation-context.js";
|
|
@@ -42,8 +37,8 @@ export function isSimpleDataAccess(node: MemberExpression, dataKeys: ReadonlySet
|
|
|
42
37
|
return false;
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
export function extractPath(node: MemberExpression): string
|
|
46
|
-
const path: string
|
|
40
|
+
export function extractPath(node: MemberExpression): Array<string> {
|
|
41
|
+
const path: Array<string> = [];
|
|
47
42
|
|
|
48
43
|
if (node.property.type === "Identifier") {
|
|
49
44
|
path.unshift(node.property.name);
|
|
@@ -70,7 +65,8 @@ export function extractPath(node: MemberExpression): string[] {
|
|
|
70
65
|
return path;
|
|
71
66
|
}
|
|
72
67
|
|
|
73
|
-
export function getAvailablePropsAtPath(path: string
|
|
68
|
+
export function getAvailablePropsAtPath(path: Array<string>, depth: number, schemaDataParam: unknown): Array<string> {
|
|
69
|
+
const schemaData = schemaDataParam as Record<string, PropertySchema> | undefined;
|
|
74
70
|
if (!schemaData || depth < 0) {
|
|
75
71
|
return schemaData ? Object.keys(schemaData) : [];
|
|
76
72
|
}
|
|
@@ -79,16 +75,16 @@ export function getAvailablePropsAtPath(path: string[], depth: number, schemaDat
|
|
|
79
75
|
return Object.keys(schemaData);
|
|
80
76
|
}
|
|
81
77
|
|
|
82
|
-
let current = schemaData[path[0]];
|
|
78
|
+
let current: PropertySchema | undefined = schemaData[path[0]];
|
|
83
79
|
|
|
84
80
|
for (let i = 1; i <= depth; i++) {
|
|
85
|
-
if (
|
|
81
|
+
if (current?.type !== "object" || !current.shape) {
|
|
86
82
|
return [];
|
|
87
83
|
}
|
|
88
84
|
current = current.shape[path[i]];
|
|
89
85
|
}
|
|
90
86
|
|
|
91
|
-
if (current
|
|
87
|
+
if (current?.type === "object" && current.shape) {
|
|
92
88
|
return Object.keys(current.shape);
|
|
93
89
|
}
|
|
94
90
|
|
|
@@ -96,7 +92,7 @@ export function getAvailablePropsAtPath(path: string[], depth: number, schemaDat
|
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
export function validateSchemaPath(
|
|
99
|
-
path: string
|
|
95
|
+
path: Array<string>,
|
|
100
96
|
schema: Schema,
|
|
101
97
|
analysisReport: AnalysisReport,
|
|
102
98
|
node: MemberExpression,
|
|
@@ -268,12 +264,12 @@ export function validateSchemaPath(
|
|
|
268
264
|
}
|
|
269
265
|
}
|
|
270
266
|
|
|
271
|
-
function getElementAccessFlags(node: MemberExpression, pathLength: number): boolean
|
|
267
|
+
function getElementAccessFlags(node: MemberExpression, pathLength: number): Array<boolean> {
|
|
272
268
|
const flags = Array(pathLength).fill(false);
|
|
273
|
-
const segments: Array<{ property:
|
|
269
|
+
const segments: Array<{ property: Expression | PrivateIdentifier; computed: boolean }> = [];
|
|
274
270
|
|
|
275
|
-
let current:
|
|
276
|
-
while (current
|
|
271
|
+
let current: Expression | Super | undefined = node;
|
|
272
|
+
while (current?.type === "MemberExpression") {
|
|
277
273
|
segments.unshift({ property: current.property, computed: current.computed });
|
|
278
274
|
current = current.object;
|
|
279
275
|
}
|
package/src/analyze/index.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { Program } from "acorn";
|
|
2
2
|
import type { Schema } from "../../schema.js";
|
|
3
|
-
import { AnalysisReport } from "../analysis-report.js";
|
|
4
|
-
import {
|
|
3
|
+
import { createAnalysisReport, type AnalysisReport } from "../analysis-report.js";
|
|
4
|
+
import { createValidationContext } from "../validation-context.js";
|
|
5
5
|
import { analyzeElementAttributes } from "./element-attributes.js";
|
|
6
6
|
import { analyzeElementChildren } from "./element-children.js";
|
|
7
7
|
import { analyzeElementTags } from "./element-tags.js";
|
|
8
8
|
|
|
9
9
|
export function analyzeJsx(ast: Program, schema: Schema): AnalysisReport {
|
|
10
|
-
const validationContext =
|
|
10
|
+
const validationContext = createValidationContext();
|
|
11
11
|
|
|
12
|
-
return
|
|
12
|
+
return createAnalysisReport().merge(
|
|
13
13
|
analyzeElementTags(ast, schema, validationContext),
|
|
14
14
|
analyzeElementAttributes(ast, schema, validationContext),
|
|
15
15
|
analyzeElementChildren(ast, schema, validationContext)
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import type { Program } from "acorn";
|
|
2
2
|
import { isJsxAttribute, isJsxExpressionContainer, type JSXAttribute } from "../../jsx.js";
|
|
3
|
-
import {
|
|
4
|
-
getAllowedAttributes,
|
|
5
|
-
getAttributeSchema,
|
|
6
|
-
getEnumValues,
|
|
7
|
-
getRequiredAttributes,
|
|
8
|
-
isAttributeAllowed,
|
|
9
|
-
type Schema,
|
|
10
|
-
} from "../../schema.js";
|
|
3
|
+
import { getAllowedAttributes, getAttributeSchema, getEnumValues, getRequiredAttributes, isAttributeAllowed, type Schema } from "../../schema.js";
|
|
11
4
|
import { traverse } from "../../traverse.js";
|
|
12
|
-
import { AnalysisReport } from "../analysis-report.js";
|
|
5
|
+
import { createAnalysisReport, type AnalysisReport } from "../analysis-report.js";
|
|
13
6
|
import { getAttributeSimilarityMatchers, getBestSimilarityMatcherSuggestion, getNodeRange } from "../utils.js";
|
|
14
7
|
import type { ValidationContext } from "../validation-context.js";
|
|
15
8
|
|
|
16
|
-
export function analyzeElementAttributes(
|
|
17
|
-
|
|
18
|
-
schema: Schema,
|
|
19
|
-
validationContext: ValidationContext
|
|
20
|
-
): AnalysisReport {
|
|
21
|
-
const analysisReport = new AnalysisReport();
|
|
9
|
+
export function analyzeElementAttributes(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
|
|
10
|
+
const analysisReport = createAnalysisReport();
|
|
22
11
|
|
|
23
12
|
const localFunctionNames = new Set<string>();
|
|
24
13
|
for (const stmt of ast.body) {
|
|
@@ -87,9 +76,7 @@ export function analyzeElementAttributes(
|
|
|
87
76
|
const stringEnumValues = enumValues.map((value) => String(value));
|
|
88
77
|
const suggestionMatch = getBestSimilarityMatcherSuggestion(String(literalValue), stringEnumValues);
|
|
89
78
|
|
|
90
|
-
let message = `Value ${JSON.stringify(
|
|
91
|
-
literalValue
|
|
92
|
-
)} is not allowed for attribute "${attributeName}" on <${tagName}>`;
|
|
79
|
+
let message = `Value ${JSON.stringify(literalValue)} is not allowed for attribute "${attributeName}" on <${tagName}>`;
|
|
93
80
|
|
|
94
81
|
if (suggestionMatch) {
|
|
95
82
|
message += `. Did you mean "${suggestionMatch}"?`;
|