jsxpression 0.1.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/LICENSE +21 -0
- package/README.md +368 -0
- package/lib/analyze/analysis-error.d.ts.map +1 -0
- package/lib/analyze/analysis-report.d.ts +33 -0
- package/lib/analyze/analysis-report.d.ts.map +1 -0
- package/lib/analyze/analysis-report.js +194 -0
- package/lib/analyze/analysis-report.js.map +1 -0
- package/lib/analyze/analyze.d.ts.map +1 -0
- package/lib/analyze/analyze.test.d.ts.map +1 -0
- package/lib/analyze/analyze.test.js +765 -0
- package/lib/analyze/analyze.test.js.map +1 -0
- package/lib/analyze/data/analyze.d.ts.map +1 -0
- package/lib/analyze/data/data-access.d.ts.map +1 -0
- package/lib/analyze/data/index.d.ts.map +1 -0
- package/lib/analyze/data/method-calls.d.ts.map +1 -0
- package/lib/analyze/data/utils.d.ts.map +1 -0
- package/lib/analyze/index.d.ts.map +1 -0
- package/lib/analyze/jsx/analyze.d.ts.map +1 -0
- package/lib/analyze/jsx/element-attributes.d.ts.map +1 -0
- package/lib/analyze/jsx/element-attributes.js +89 -0
- package/lib/analyze/jsx/element-attributes.js.map +1 -0
- package/lib/analyze/jsx/element-children.d.ts.map +1 -0
- package/lib/analyze/jsx/element-tags.d.ts.map +1 -0
- package/lib/analyze/jsx/index.d.ts.map +1 -0
- package/lib/analyze/security/analyze.d.ts.map +1 -0
- package/lib/analyze/security/call-expressions.d.ts.map +1 -0
- package/lib/analyze/security/declarations.d.ts.map +1 -0
- package/lib/analyze/security/expressions.d.ts.map +1 -0
- package/lib/analyze/security/identifiers.d.ts.map +1 -0
- package/lib/analyze/security/index.d.ts.map +1 -0
- package/lib/analyze/security/member-access.d.ts.map +1 -0
- package/lib/builtins.d.ts +50 -0
- package/lib/builtins.d.ts.map +1 -0
- package/lib/builtins.js +1065 -0
- package/lib/builtins.js.map +1 -0
- package/lib/codegen/builtin-types.d.ts +11 -0
- package/lib/codegen/builtin-types.d.ts.map +1 -0
- package/lib/codegen/builtin-types.js +151 -0
- package/lib/codegen/builtin-types.js.map +1 -0
- package/lib/codegen/builtins/builtin-types.d.ts +2 -0
- package/lib/codegen/builtins/builtin-types.d.ts.map +1 -0
- package/lib/codegen/builtins/builtin-types.js +143 -0
- package/lib/codegen/builtins/builtin-types.js.map +1 -0
- package/lib/codegen/builtins/index.d.ts +2 -0
- package/lib/codegen/builtins/index.d.ts.map +1 -0
- package/lib/codegen/builtins/index.js +6 -0
- package/lib/codegen/builtins/index.js.map +1 -0
- package/lib/codegen/declare-types.d.ts +20 -0
- package/lib/codegen/declare-types.d.ts.map +1 -0
- package/lib/codegen/declare-types.js +299 -0
- package/lib/codegen/declare-types.js.map +1 -0
- package/lib/codegen/generate-typescript-definitions.d.ts +31 -0
- package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -0
- package/lib/codegen/generate-typescript-definitions.js +37 -0
- package/lib/codegen/generate-typescript-definitions.js.map +1 -0
- package/lib/codegen/generate-typescript-definitions.test.d.ts +2 -0
- package/lib/codegen/generate-typescript-definitions.test.d.ts.map +1 -0
- package/lib/codegen/generate-typescript-definitions.test.js +587 -0
- package/lib/codegen/generate-typescript-definitions.test.js.map +1 -0
- package/lib/codegen/index.d.ts +2 -0
- package/lib/codegen/index.d.ts.map +1 -0
- package/lib/codegen/index.js +6 -0
- package/lib/codegen/index.js.map +1 -0
- package/lib/codegen/schema/component-types.d.ts +3 -0
- package/lib/codegen/schema/component-types.d.ts.map +1 -0
- package/lib/codegen/schema/component-types.js +118 -0
- package/lib/codegen/schema/component-types.js.map +1 -0
- package/lib/codegen/schema/data-types.d.ts +3 -0
- package/lib/codegen/schema/data-types.d.ts.map +1 -0
- package/lib/codegen/schema/data-types.js +98 -0
- package/lib/codegen/schema/data-types.js.map +1 -0
- package/lib/codegen/schema/index.d.ts +3 -0
- package/lib/codegen/schema/index.d.ts.map +1 -0
- package/lib/codegen/schema/index.js +8 -0
- package/lib/codegen/schema/index.js.map +1 -0
- package/lib/codegen/schema/utils.d.ts +4 -0
- package/lib/codegen/schema/utils.d.ts.map +1 -0
- package/lib/codegen/schema/utils.js +103 -0
- package/lib/codegen/schema/utils.js.map +1 -0
- package/lib/codegen/schema-to-types.d.ts +14 -0
- package/lib/codegen/schema-to-types.d.ts.map +1 -0
- package/lib/codegen/schema-to-types.js +203 -0
- package/lib/codegen/schema-to-types.js.map +1 -0
- package/lib/compile/compile.d.ts.map +1 -0
- package/lib/compile/compile.js +245 -0
- package/lib/compile/compile.js.map +1 -0
- package/lib/compile/compile.test.d.ts.map +1 -0
- package/lib/compile/compile.test.js +148 -0
- package/lib/compile/compile.test.js.map +1 -0
- package/lib/evaluate/evaluate.d.ts +26 -0
- package/lib/evaluate/evaluate.d.ts.map +1 -0
- package/lib/evaluate/evaluate.js +40 -0
- package/lib/evaluate/evaluation-error.d.ts +4 -0
- package/lib/evaluate/evaluation-error.d.ts.map +1 -0
- package/lib/evaluate/evaluation-error.js +6 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -0
- package/lib/index.js.map +1 -0
- package/lib/render.d.ts +58 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +52 -0
- package/lib/render.test.d.ts.map +1 -0
- package/lib/schema.d.ts +68 -0
- package/lib/schema.d.ts.map +1 -0
- package/lib/schema.js +106 -0
- package/lib/validate.d.ts +41 -0
- package/lib/validate.d.ts.map +1 -0
- package/lib/validate.js +42 -0
- package/package.json +27 -0
- package/src/analyze/analysis-error.ts +26 -0
- package/src/analyze/analysis-report.ts +228 -0
- package/src/analyze/analyze.test.ts +829 -0
- package/src/analyze/analyze.ts +10 -0
- package/src/analyze/data/analyze.ts +15 -0
- package/src/analyze/data/data-access.ts +349 -0
- package/src/analyze/data/index.ts +1 -0
- package/src/analyze/data/method-calls.ts +96 -0
- package/src/analyze/data/utils.ts +329 -0
- package/src/analyze/index.ts +3 -0
- package/src/analyze/jsx/analyze.ts +17 -0
- package/src/analyze/jsx/element-attributes.ts +143 -0
- package/src/analyze/jsx/element-children.ts +97 -0
- package/src/analyze/jsx/element-tags.ts +55 -0
- package/src/analyze/jsx/index.ts +1 -0
- package/src/analyze/security/analyze.ts +21 -0
- package/src/analyze/security/call-expressions.ts +41 -0
- package/src/analyze/security/declarations.ts +67 -0
- package/src/analyze/security/expressions.ts +100 -0
- package/src/analyze/security/identifiers.ts +113 -0
- package/src/analyze/security/index.ts +1 -0
- package/src/analyze/security/member-access.ts +35 -0
- package/src/analyze/utils.ts +118 -0
- package/src/analyze/validation-context.ts +32 -0
- package/src/builtins.ts +1149 -0
- package/src/codegen/builtins/builtin-types.ts +174 -0
- package/src/codegen/builtins/index.ts +1 -0
- package/src/codegen/generate-typescript-definitions.test.ts +676 -0
- package/src/codegen/generate-typescript-definitions.ts +35 -0
- package/src/codegen/index.ts +1 -0
- package/src/codegen/schema/component-types.ts +140 -0
- package/src/codegen/schema/data-types.ts +109 -0
- package/src/codegen/schema/index.ts +2 -0
- package/src/codegen/schema/utils.ts +111 -0
- package/src/compile/compilation-error.ts +37 -0
- package/src/compile/compile.test.ts +180 -0
- package/src/compile/compile.ts +282 -0
- package/src/compile/index.ts +2 -0
- package/src/evaluate/evaluate.test.ts +316 -0
- package/src/evaluate/evaluate.ts +81 -0
- package/src/evaluate/evaluation-error.ts +6 -0
- package/src/evaluate/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/jsx.ts +161 -0
- package/src/parse/index.ts +2 -0
- package/src/parse/parse-error.ts +14 -0
- package/src/parse/parse.test.ts +99 -0
- package/src/parse/parse.ts +40 -0
- package/src/render.test.ts +425 -0
- package/src/render.ts +75 -0
- package/src/schema.ts +195 -0
- package/src/traverse.test.ts +104 -0
- package/src/traverse.ts +78 -0
- package/src/types.ts +17 -0
- package/src/validate.ts +59 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Program } from "acorn";
|
|
2
|
+
import type { Schema } from "../schema";
|
|
3
|
+
import { AnalysisReport } from "./analysis-report";
|
|
4
|
+
import { analyzeSecurity } from "./security";
|
|
5
|
+
import { analyzeJsx } from "./jsx";
|
|
6
|
+
import { analyzeData } from "./data";
|
|
7
|
+
|
|
8
|
+
export function analyze(ast: Program, schema: Schema): AnalysisReport {
|
|
9
|
+
return new AnalysisReport().merge(analyzeSecurity(ast, schema), analyzeJsx(ast, schema), analyzeData(ast, schema));
|
|
10
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Program } from "acorn";
|
|
2
|
+
import { Schema } from "../../schema";
|
|
3
|
+
import { AnalysisReport } from "../analysis-report";
|
|
4
|
+
import { ValidationContext } from "../validation-context";
|
|
5
|
+
import { analyzeDataAccess } from "./data-access";
|
|
6
|
+
import { analyzeMethodCalls } from "./method-calls";
|
|
7
|
+
|
|
8
|
+
export function analyzeData(ast: Program, schema: Schema): AnalysisReport {
|
|
9
|
+
const validationContext = new ValidationContext();
|
|
10
|
+
|
|
11
|
+
return new AnalysisReport().merge(
|
|
12
|
+
analyzeMethodCalls(ast, schema, validationContext),
|
|
13
|
+
analyzeDataAccess(ast, schema, validationContext)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import type { Program } from "acorn";
|
|
2
|
+
import { traverse } from "../../traverse";
|
|
3
|
+
import type { ArrayPropertySchema, PropertySchema, Schema } from "../../schema";
|
|
4
|
+
import { ValidationContext } from "../validation-context";
|
|
5
|
+
import { AnalysisReport } from "../analysis-report";
|
|
6
|
+
import { isSimpleDataAccess, extractPath, getParentNode, validateSchemaPath } from "./utils";
|
|
7
|
+
import { getNodeRange } from "../utils";
|
|
8
|
+
|
|
9
|
+
export function analyzeDataAccess(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
|
|
10
|
+
const analysisReport = new AnalysisReport();
|
|
11
|
+
|
|
12
|
+
const dataPaths = new Set<string>();
|
|
13
|
+
const arrowFunctionContexts = new Map<any, Map<string, any>>();
|
|
14
|
+
|
|
15
|
+
// First pass: collect all data paths and arrow function contexts (including nested ones)
|
|
16
|
+
function collectArrowFunctionContexts(node: any): void {
|
|
17
|
+
if (node.type === "CallExpression") {
|
|
18
|
+
const { callee, arguments: args } = node;
|
|
19
|
+
|
|
20
|
+
if (
|
|
21
|
+
callee.type === "MemberExpression" &&
|
|
22
|
+
!callee.computed &&
|
|
23
|
+
callee.property.type === "Identifier" &&
|
|
24
|
+
args.length > 0 &&
|
|
25
|
+
args[0].type === "ArrowFunctionExpression"
|
|
26
|
+
) {
|
|
27
|
+
const method = callee.property.name;
|
|
28
|
+
|
|
29
|
+
// TODO: Move these into builtins so it stays in sync with the global method definitions
|
|
30
|
+
const arrayCallbackMethods = ["map", "filter", "find", "forEach", "some", "every", "reduce"];
|
|
31
|
+
|
|
32
|
+
if (arrayCallbackMethods.includes(method)) {
|
|
33
|
+
const arrayElementType = getArrayElementTypeFromCall(node, schema, arrowFunctionContexts);
|
|
34
|
+
const arrowFunction = args[0];
|
|
35
|
+
|
|
36
|
+
if (arrayElementType && arrowFunction.params.length > 0) {
|
|
37
|
+
const parameterTypes = new Map();
|
|
38
|
+
|
|
39
|
+
// First parameter is always the array element
|
|
40
|
+
if (arrowFunction.params[0].type === "Identifier") {
|
|
41
|
+
parameterTypes.set(arrowFunction.params[0].name, arrayElementType);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// For methods like reduce, second parameter might be index (number)
|
|
45
|
+
if (arrowFunction.params.length > 1 && arrowFunction.params[1].type === "Identifier") {
|
|
46
|
+
parameterTypes.set(arrowFunction.params[1].name, { type: "number" });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
arrowFunctionContexts.set(arrowFunction, parameterTypes);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursively traverse all child nodes
|
|
56
|
+
for (const key of Object.keys(node)) {
|
|
57
|
+
const value = node[key];
|
|
58
|
+
if (value && typeof value === "object") {
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const child of value) {
|
|
61
|
+
if (child && typeof child === "object" && child.type) {
|
|
62
|
+
collectArrowFunctionContexts(child);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (value.type) {
|
|
66
|
+
collectArrowFunctionContexts(value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
traverse(ast, {
|
|
73
|
+
MemberExpression(node) {
|
|
74
|
+
if (isSimpleDataAccess(node)) {
|
|
75
|
+
const path = extractPath(node);
|
|
76
|
+
if (path[0] === "data") {
|
|
77
|
+
dataPaths.add(path.join("."));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Collect all arrow function contexts including nested ones
|
|
84
|
+
collectArrowFunctionContexts(ast);
|
|
85
|
+
|
|
86
|
+
const validatablePaths = new Set<string>();
|
|
87
|
+
|
|
88
|
+
for (const path of dataPaths) {
|
|
89
|
+
let hasLongerPath = false;
|
|
90
|
+
for (const otherPath of dataPaths) {
|
|
91
|
+
if (otherPath !== path && otherPath.startsWith(path + ".")) {
|
|
92
|
+
hasLongerPath = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!hasLongerPath) {
|
|
97
|
+
validatablePaths.add(path);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Second pass: validate with context awareness
|
|
102
|
+
traverse(ast, {
|
|
103
|
+
MemberExpression(node) {
|
|
104
|
+
const parent = getParentNode(node, ast);
|
|
105
|
+
if (parent && parent.type === "CallExpression" && parent.callee === node) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Check if this is inside an arrow function with parameter context
|
|
110
|
+
const arrowFunctionContext = findArrowFunctionContext(node, ast, arrowFunctionContexts);
|
|
111
|
+
|
|
112
|
+
if (arrowFunctionContext && node.object.type === "Identifier") {
|
|
113
|
+
const paramType = arrowFunctionContext.get(node.object.name);
|
|
114
|
+
if (paramType) {
|
|
115
|
+
validateParameterAccess(node, paramType, analysisReport, validationContext);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isSimpleDataAccess(node)) {
|
|
121
|
+
const path = extractPath(node);
|
|
122
|
+
|
|
123
|
+
if (path[0] === "data") {
|
|
124
|
+
const pathString = path.join(".");
|
|
125
|
+
|
|
126
|
+
if (validatablePaths.has(pathString)) {
|
|
127
|
+
const schemaPath = path.slice(1);
|
|
128
|
+
validateSchemaPath(schemaPath, schema, analysisReport, node, validationContext);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return analysisReport;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function findArrowFunctionContext(
|
|
139
|
+
targetNode: any,
|
|
140
|
+
ast: Program,
|
|
141
|
+
arrowFunctionContexts: Map<any, Map<string, any>>
|
|
142
|
+
): Map<string, any> | null {
|
|
143
|
+
function findInNode(node: any, depth: number = 0): any {
|
|
144
|
+
if (node === targetNode) {
|
|
145
|
+
return { found: true, depth };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (node.type === "ArrowFunctionExpression" && arrowFunctionContexts.has(node)) {
|
|
149
|
+
const result = findInNode(node.body, depth + 1);
|
|
150
|
+
if (result?.found) {
|
|
151
|
+
return { arrowFunction: node, depth: result.depth };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const key of Object.keys(node)) {
|
|
156
|
+
const value = node[key];
|
|
157
|
+
if (value && typeof value === "object") {
|
|
158
|
+
if (Array.isArray(value)) {
|
|
159
|
+
for (const child of value) {
|
|
160
|
+
if (child && typeof child === "object" && child.type) {
|
|
161
|
+
const result = findInNode(child, depth);
|
|
162
|
+
if (result?.found || result?.arrowFunction) {
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else if (value.type) {
|
|
168
|
+
const result = findInNode(value, depth);
|
|
169
|
+
if (result?.found || result?.arrowFunction) {
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const result = findInNode(ast);
|
|
179
|
+
return result?.arrowFunction ? arrowFunctionContexts.get(result.arrowFunction) || null : null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function getArrayElementTypeFromCall(
|
|
183
|
+
node: any,
|
|
184
|
+
schema: Schema,
|
|
185
|
+
arrowFunctionContexts: Map<any, Map<string, any>>
|
|
186
|
+
): any {
|
|
187
|
+
const { callee } = node;
|
|
188
|
+
|
|
189
|
+
if (callee.type !== "MemberExpression") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (isSimpleDataAccess(callee)) {
|
|
194
|
+
const path = extractPath(callee);
|
|
195
|
+
if (path[0] === "data") {
|
|
196
|
+
const schemaPath = path.slice(1, -1); // Remove "data" prefix and method name
|
|
197
|
+
const arraySchema = getSchemaAtPath(schemaPath, schema);
|
|
198
|
+
|
|
199
|
+
if (arraySchema?.type === "array") {
|
|
200
|
+
return getArrayElementType(arraySchema);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else if (callee.object.type === "MemberExpression") {
|
|
204
|
+
// Handle nested access like group.items.map where group is a parameter
|
|
205
|
+
const objectPath = extractPath(callee.object);
|
|
206
|
+
|
|
207
|
+
// Check if this is a parameter access (not data access)
|
|
208
|
+
if (objectPath.length === 2) {
|
|
209
|
+
// e.g., ["group", "items"] or ["dept", "employees"]
|
|
210
|
+
const paramName = objectPath[0];
|
|
211
|
+
const propertyName = objectPath[1];
|
|
212
|
+
|
|
213
|
+
// Find the arrow function context that defines this parameter
|
|
214
|
+
const parameterType = resolveParameterType(paramName, node, arrowFunctionContexts);
|
|
215
|
+
|
|
216
|
+
if (parameterType && parameterType.type === "object" && parameterType.shape) {
|
|
217
|
+
const propertySchema = parameterType.shape[propertyName];
|
|
218
|
+
if (propertySchema && propertySchema.type === "array") {
|
|
219
|
+
return getArrayElementType(propertySchema);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Fallback: try to find any matching property in schema
|
|
224
|
+
if (propertyName === "items" || propertyName === "employees") {
|
|
225
|
+
const itemsArraySchema = findArrayPropertyInSchema(schema, propertyName);
|
|
226
|
+
if (itemsArraySchema) {
|
|
227
|
+
return getArrayElementType(itemsArraySchema);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function resolveParameterType(
|
|
237
|
+
paramName: string,
|
|
238
|
+
currentNode: any,
|
|
239
|
+
arrowFunctionContexts: Map<any, Map<string, any>>
|
|
240
|
+
): any {
|
|
241
|
+
// Find the arrow function that contains this node and has the parameter
|
|
242
|
+
for (const [arrowFunction, parameterTypes] of arrowFunctionContexts.entries()) {
|
|
243
|
+
if (parameterTypes.has(paramName)) {
|
|
244
|
+
// Check if currentNode is inside this arrow function
|
|
245
|
+
if (isNodeInsideArrowFunction(currentNode, arrowFunction)) {
|
|
246
|
+
return parameterTypes.get(paramName);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isNodeInsideArrowFunction(targetNode: any, arrowFunction: any): boolean {
|
|
254
|
+
// Simple check: if the target node's position is within the arrow function's range
|
|
255
|
+
if (targetNode.start >= arrowFunction.start && targetNode.end <= arrowFunction.end) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function findArrayPropertyInSchema(schema: Schema, propertyName: string): any {
|
|
262
|
+
function searchInObject(obj: any): any {
|
|
263
|
+
if (!obj || typeof obj !== "object") return null;
|
|
264
|
+
|
|
265
|
+
if (obj.type === "object" && obj.shape) {
|
|
266
|
+
// Check if this object has the property we're looking for
|
|
267
|
+
if (obj.shape[propertyName] && obj.shape[propertyName].type === "array") {
|
|
268
|
+
return obj.shape[propertyName];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Recursively search in nested objects
|
|
272
|
+
for (const key of Object.keys(obj.shape)) {
|
|
273
|
+
const result = searchInObject(obj.shape[key]);
|
|
274
|
+
if (result) return result;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (obj.type === "array" && obj.shape) {
|
|
279
|
+
return searchInObject(obj.shape);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!schema.data) return null;
|
|
286
|
+
|
|
287
|
+
for (const key of Object.keys(schema.data)) {
|
|
288
|
+
const result = searchInObject(schema.data[key]);
|
|
289
|
+
if (result) return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getSchemaAtPath(path: string[], schema: Schema): any {
|
|
296
|
+
if (!schema.data || path.length === 0) {
|
|
297
|
+
return schema.data;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let current = schema.data[path[0]];
|
|
301
|
+
|
|
302
|
+
for (let i = 1; i < path.length && current; i++) {
|
|
303
|
+
if (current.type === "object" && current.shape) {
|
|
304
|
+
current = current.shape[path[i]];
|
|
305
|
+
} else {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return current;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function validateParameterAccess(
|
|
314
|
+
node: any,
|
|
315
|
+
paramType: any,
|
|
316
|
+
analysisReport: AnalysisReport,
|
|
317
|
+
validationContext: ValidationContext
|
|
318
|
+
): void {
|
|
319
|
+
if (node.property.type !== "Identifier") {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const propertyName = node.property.name;
|
|
324
|
+
|
|
325
|
+
if (paramType.type === "object" && paramType.shape) {
|
|
326
|
+
if (!paramType.shape[propertyName]) {
|
|
327
|
+
analysisReport.addIssue(
|
|
328
|
+
"INVALID_PARAMETER_ACCESS",
|
|
329
|
+
`Property '${propertyName}' does not exist on parameter '${node.object.name}'`,
|
|
330
|
+
getNodeRange(node),
|
|
331
|
+
validationContext.getSnapshot(),
|
|
332
|
+
Object.keys(paramType.shape)
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
analysisReport.addIssue(
|
|
337
|
+
"INVALID_PARAMETER_ACCESS",
|
|
338
|
+
`Cannot access property '${propertyName}' on ${paramType.type || "undefined"} type parameter '${
|
|
339
|
+
node.object.name
|
|
340
|
+
}'`,
|
|
341
|
+
getNodeRange(node),
|
|
342
|
+
validationContext.getSnapshot()
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function getArrayElementType(schema: ArrayPropertySchema): PropertySchema | null {
|
|
348
|
+
return schema.shape || null;
|
|
349
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { analyzeData } from "./analyze";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Program } from "acorn";
|
|
2
|
+
import { traverse } from "../../traverse";
|
|
3
|
+
import type { Schema } from "../../schema";
|
|
4
|
+
import { isMethodAllowed, getMethodDefinition, getMethodDisplayName } from "../../builtins";
|
|
5
|
+
import { ValidationContext } from "../validation-context";
|
|
6
|
+
import { AnalysisReport } from "../analysis-report";
|
|
7
|
+
import { isSimpleDataAccess, extractPath, validateSchemaPath } from "./utils";
|
|
8
|
+
import { getNodeRange } from "../utils";
|
|
9
|
+
|
|
10
|
+
export function analyzeMethodCalls(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
|
|
11
|
+
const analysisReport = new AnalysisReport();
|
|
12
|
+
|
|
13
|
+
traverse(ast, {
|
|
14
|
+
CallExpression(node) {
|
|
15
|
+
const { callee, arguments: args } = node;
|
|
16
|
+
|
|
17
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
|
|
18
|
+
const method = callee.property.name;
|
|
19
|
+
|
|
20
|
+
if (isSimpleDataAccess(callee)) {
|
|
21
|
+
const path = extractPath(callee);
|
|
22
|
+
if (path[0] === "data") {
|
|
23
|
+
const schemaPath = path.slice(1, -1);
|
|
24
|
+
if (schemaPath.length > 0) {
|
|
25
|
+
validateSchemaPath(schemaPath, schema, analysisReport, callee, validationContext);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!isMethodAllowed(method)) {
|
|
31
|
+
const methodContext = callee.object.type === "Identifier" ? `${callee.object.name}.${method}` : `.${method}`;
|
|
32
|
+
|
|
33
|
+
analysisReport.addIssue(
|
|
34
|
+
"METHOD_NOT_ALLOWED",
|
|
35
|
+
`method ${methodContext} not allowed`,
|
|
36
|
+
getNodeRange(callee),
|
|
37
|
+
validationContext.getSnapshot()
|
|
38
|
+
);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const methodDef = getMethodDefinition(method);
|
|
43
|
+
|
|
44
|
+
if (!methodDef || !methodDef.params) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { params } = methodDef;
|
|
49
|
+
|
|
50
|
+
const requiredParams = params.filter((params) => params.required !== false);
|
|
51
|
+
const hasVariadic = params.some((params) => params.variadic);
|
|
52
|
+
|
|
53
|
+
if (hasVariadic) {
|
|
54
|
+
const nonVariadicRequired = requiredParams.filter((params) => !params.variadic);
|
|
55
|
+
if (args.length < nonVariadicRequired.length) {
|
|
56
|
+
analysisReport.addIssue(
|
|
57
|
+
"INSUFFICIENT_PARAMETERS",
|
|
58
|
+
`${getMethodDisplayName(method)} expects at least ${nonVariadicRequired.length} parameter${
|
|
59
|
+
nonVariadicRequired.length !== 1 ? "s" : ""
|
|
60
|
+
}, got ${args.length}`,
|
|
61
|
+
getNodeRange(node),
|
|
62
|
+
validationContext.getSnapshot()
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
const minParams = requiredParams.length;
|
|
67
|
+
const maxParams = params.length;
|
|
68
|
+
|
|
69
|
+
if (args.length < minParams) {
|
|
70
|
+
analysisReport.addIssue(
|
|
71
|
+
"INSUFFICIENT_PARAMETERS",
|
|
72
|
+
`${getMethodDisplayName(method)} expects at least ${minParams} parameter${
|
|
73
|
+
minParams !== 1 ? "s" : ""
|
|
74
|
+
}, got ${args.length}`,
|
|
75
|
+
getNodeRange(node),
|
|
76
|
+
validationContext.getSnapshot()
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (args.length > maxParams) {
|
|
81
|
+
analysisReport.addIssue(
|
|
82
|
+
"TOO_MANY_PARAMETERS",
|
|
83
|
+
`${getMethodDisplayName(method)} expects at most ${maxParams} parameter${
|
|
84
|
+
maxParams !== 1 ? "s" : ""
|
|
85
|
+
}, got ${args.length}`,
|
|
86
|
+
getNodeRange(node),
|
|
87
|
+
validationContext.getSnapshot()
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return analysisReport;
|
|
96
|
+
}
|