jsxpression 0.1.27 → 0.1.29

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.
Files changed (141) hide show
  1. package/README.md +155 -188
  2. package/lib/analyze/analysis-report.d.ts +2 -2
  3. package/lib/analyze/analysis-report.d.ts.map +1 -1
  4. package/lib/analyze/analysis-report.js +4 -0
  5. package/lib/analyze/analysis-report.js.map +1 -1
  6. package/lib/analyze/data/data-access.d.ts.map +1 -1
  7. package/lib/analyze/data/data-access.js +45 -82
  8. package/lib/analyze/data/data-access.js.map +1 -1
  9. package/lib/analyze/data/method-calls.d.ts.map +1 -1
  10. package/lib/analyze/data/method-calls.js +4 -3
  11. package/lib/analyze/data/method-calls.js.map +1 -1
  12. package/lib/analyze/data/utils.d.ts +2 -3
  13. package/lib/analyze/data/utils.d.ts.map +1 -1
  14. package/lib/analyze/data/utils.js +10 -36
  15. package/lib/analyze/data/utils.js.map +1 -1
  16. package/lib/analyze/jsx/element-attributes.d.ts +1 -1
  17. package/lib/analyze/jsx/element-attributes.d.ts.map +1 -1
  18. package/lib/analyze/jsx/element-attributes.js +13 -3
  19. package/lib/analyze/jsx/element-attributes.js.map +1 -1
  20. package/lib/analyze/jsx/element-children.d.ts +1 -1
  21. package/lib/analyze/jsx/element-children.d.ts.map +1 -1
  22. package/lib/analyze/jsx/element-children.js +16 -5
  23. package/lib/analyze/jsx/element-children.js.map +1 -1
  24. package/lib/analyze/jsx/element-tags.d.ts +1 -1
  25. package/lib/analyze/jsx/element-tags.d.ts.map +1 -1
  26. package/lib/analyze/jsx/element-tags.js +12 -2
  27. package/lib/analyze/jsx/element-tags.js.map +1 -1
  28. package/lib/analyze/security/analyze.d.ts.map +1 -1
  29. package/lib/analyze/security/analyze.js +2 -4
  30. package/lib/analyze/security/analyze.js.map +1 -1
  31. package/lib/analyze/security/call-expressions.d.ts +2 -2
  32. package/lib/analyze/security/call-expressions.d.ts.map +1 -1
  33. package/lib/analyze/security/call-expressions.js +21 -5
  34. package/lib/analyze/security/call-expressions.js.map +1 -1
  35. package/lib/analyze/security/declarations.d.ts +1 -1
  36. package/lib/analyze/security/declarations.d.ts.map +1 -1
  37. package/lib/analyze/security/declarations.js +22 -2
  38. package/lib/analyze/security/declarations.js.map +1 -1
  39. package/lib/analyze/security/expressions.d.ts.map +1 -1
  40. package/lib/analyze/security/expressions.js +10 -2
  41. package/lib/analyze/security/expressions.js.map +1 -1
  42. package/lib/analyze/security/identifiers.d.ts +2 -2
  43. package/lib/analyze/security/identifiers.d.ts.map +1 -1
  44. package/lib/analyze/security/identifiers.js +159 -19
  45. package/lib/analyze/security/identifiers.js.map +1 -1
  46. package/lib/builtins.d.ts.map +1 -1
  47. package/lib/builtins.js +5 -0
  48. package/lib/builtins.js.map +1 -1
  49. package/lib/codegen/schema/utils.d.ts.map +1 -1
  50. package/lib/codegen/schema/utils.js +7 -1
  51. package/lib/codegen/schema/utils.js.map +1 -1
  52. package/lib/compile/compile.d.ts.map +1 -1
  53. package/lib/compile/compile.js +131 -61
  54. package/lib/compile/compile.js.map +1 -1
  55. package/lib/evaluate/evaluate.d.ts +3 -3
  56. package/lib/evaluate/evaluate.d.ts.map +1 -1
  57. package/lib/evaluate/evaluate.js +28 -7
  58. package/lib/evaluate/evaluate.js.map +1 -1
  59. package/lib/jsx.d.ts +2 -19
  60. package/lib/jsx.d.ts.map +1 -1
  61. package/lib/jsx.js +0 -6
  62. package/lib/jsx.js.map +1 -1
  63. package/lib/parse/parse.d.ts.map +1 -1
  64. package/lib/parse/parse.js +4 -3
  65. package/lib/parse/parse.js.map +1 -1
  66. package/lib/render.d.ts +4 -4
  67. package/lib/render.d.ts.map +1 -1
  68. package/lib/render.js +7 -9
  69. package/lib/render.js.map +1 -1
  70. package/lib/schema.d.ts +2 -7
  71. package/lib/schema.d.ts.map +1 -1
  72. package/lib/schema.js +0 -43
  73. package/lib/schema.js.map +1 -1
  74. package/lib/traverse.d.ts +4 -4
  75. package/lib/traverse.d.ts.map +1 -1
  76. package/lib/traverse.js +118 -40
  77. package/lib/traverse.js.map +1 -1
  78. package/lib/validate.d.ts +1 -1
  79. package/lib/validate.js +1 -1
  80. package/package.json +4 -3
  81. package/src/analyze/analysis-report.ts +6 -2
  82. package/src/analyze/analyze.test.ts +108 -69
  83. package/src/analyze/data/data-access.ts +45 -90
  84. package/src/analyze/data/method-calls.ts +4 -3
  85. package/src/analyze/data/utils.ts +11 -40
  86. package/src/analyze/jsx/element-attributes.ts +24 -9
  87. package/src/analyze/jsx/element-children.ts +20 -6
  88. package/src/analyze/jsx/element-tags.ts +15 -3
  89. package/src/analyze/security/analyze.ts +1 -3
  90. package/src/analyze/security/call-expressions.ts +37 -17
  91. package/src/analyze/security/declarations.ts +35 -9
  92. package/src/analyze/security/expressions.ts +17 -3
  93. package/src/analyze/security/identifiers.ts +172 -28
  94. package/src/builtins.ts +6 -0
  95. package/src/codegen/generate-typescript-definitions.test.ts +16 -23
  96. package/src/codegen/schema/utils.ts +9 -1
  97. package/src/compile/compile.test.ts +153 -30
  98. package/src/compile/compile.ts +163 -73
  99. package/src/evaluate/evaluate.test.ts +276 -14
  100. package/src/evaluate/evaluate.ts +34 -10
  101. package/src/jsx.ts +2 -31
  102. package/src/parse/parse.test.ts +4 -4
  103. package/src/parse/parse.ts +4 -3
  104. package/src/render.test.ts +476 -27
  105. package/src/render.ts +7 -14
  106. package/src/schema.ts +1 -55
  107. package/src/traverse.ts +135 -49
  108. package/src/validate.ts +1 -1
  109. package/LICENSE +0 -21
  110. package/lib/analyze/analyze.test.d.ts +0 -2
  111. package/lib/analyze/analyze.test.d.ts.map +0 -1
  112. package/lib/analyze/analyze.test.js +0 -763
  113. package/lib/analyze/analyze.test.js.map +0 -1
  114. package/lib/analyze/security/member-access.d.ts +0 -6
  115. package/lib/codegen/generate-typescript-definitions.test.d.ts +0 -2
  116. package/lib/codegen/generate-typescript-definitions.test.d.ts.map +0 -1
  117. package/lib/codegen/generate-typescript-definitions.test.js +0 -649
  118. package/lib/codegen/generate-typescript-definitions.test.js.map +0 -1
  119. package/lib/compile/compile.test.d.ts +0 -2
  120. package/lib/compile/compile.test.d.ts.map +0 -1
  121. package/lib/compile/compile.test.js +0 -146
  122. package/lib/compile/compile.test.js.map +0 -1
  123. package/lib/evaluate/evaluate.test.d.ts +0 -2
  124. package/lib/evaluate/evaluate.test.d.ts.map +0 -1
  125. package/lib/evaluate/evaluate.test.js +0 -260
  126. package/lib/evaluate/evaluate.test.js.map +0 -1
  127. package/lib/parse/parse.test.d.ts +0 -2
  128. package/lib/parse/parse.test.d.ts.map +0 -1
  129. package/lib/parse/parse.test.js +0 -80
  130. package/lib/parse/parse.test.js.map +0 -1
  131. package/lib/render.test.d.ts +0 -2
  132. package/lib/render.test.d.ts.map +0 -1
  133. package/lib/render.test.js +0 -301
  134. package/lib/render.test.js.map +0 -1
  135. package/lib/traverse.test.d.ts +0 -2
  136. package/lib/traverse.test.d.ts.map +0 -1
  137. package/lib/traverse.test.js +0 -84
  138. package/lib/traverse.test.js.map +0 -1
  139. package/lib/types.d.ts +0 -12
  140. package/src/analyze/security/member-access.ts +0 -35
  141. package/src/types.ts +0 -17
@@ -3,12 +3,13 @@ import { traverse } from "../../traverse.js";
3
3
  import type { ArrayPropertySchema, PropertySchema, Schema } from "../../schema.js";
4
4
  import { ValidationContext } from "../validation-context.js";
5
5
  import { AnalysisReport } from "../analysis-report.js";
6
- import { isSimpleDataAccess, extractPath, getParentNode, validateSchemaPath } from "./utils.js";
6
+ import { isSimpleDataAccess, extractPath, validateSchemaPath } from "./utils.js";
7
7
  import { getNodeRange } from "../utils.js";
8
8
 
9
9
  export function analyzeDataAccess(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
10
10
  const analysisReport = new AnalysisReport();
11
11
 
12
+ const dataKeys = new Set(Object.keys(schema.data ?? {}));
12
13
  const dataPaths = new Set<string>();
13
14
  const arrowFunctionContexts = new Map<any, Map<string, any>>();
14
15
 
@@ -71,9 +72,9 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
71
72
 
72
73
  traverse(ast, {
73
74
  MemberExpression(node) {
74
- if (isSimpleDataAccess(node)) {
75
+ if (isSimpleDataAccess(node, dataKeys)) {
75
76
  const path = extractPath(node);
76
- if (path[0] === "data") {
77
+ if (dataKeys.has(path[0])) {
77
78
  dataPaths.add(path.join("."));
78
79
  }
79
80
  }
@@ -83,6 +84,8 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
83
84
  // Collect all arrow function contexts including nested ones
84
85
  collectArrowFunctionContexts(ast);
85
86
 
87
+ const parentMap = buildParentMap(ast);
88
+
86
89
  const validatablePaths = new Set<string>();
87
90
 
88
91
  for (const path of dataPaths) {
@@ -101,13 +104,13 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
101
104
  // Second pass: validate with context awareness
102
105
  traverse(ast, {
103
106
  MemberExpression(node) {
104
- const parent = getParentNode(node, ast);
107
+ const parent = parentMap.get(node);
105
108
  if (parent && parent.type === "CallExpression" && parent.callee === node) {
106
109
  return;
107
110
  }
108
111
 
109
112
  // Check if this is inside an arrow function with parameter context
110
- const arrowFunctionContext = findArrowFunctionContext(node, ast, arrowFunctionContexts);
113
+ const arrowFunctionContext = findEnclosingArrowContext(node, arrowFunctionContexts);
111
114
 
112
115
  if (arrowFunctionContext && node.object.type === "Identifier") {
113
116
  const paramType = arrowFunctionContext.get(node.object.name);
@@ -117,15 +120,14 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
117
120
  }
118
121
  }
119
122
 
120
- if (isSimpleDataAccess(node)) {
123
+ if (isSimpleDataAccess(node, dataKeys)) {
121
124
  const path = extractPath(node);
122
125
 
123
- if (path[0] === "data") {
126
+ if (dataKeys.has(path[0])) {
124
127
  const pathString = path.join(".");
125
128
 
126
129
  if (validatablePaths.has(pathString)) {
127
- const schemaPath = path.slice(1);
128
- validateSchemaPath(schemaPath, schema, analysisReport, node, validationContext);
130
+ validateSchemaPath(path, schema, analysisReport, node, validationContext);
129
131
  }
130
132
  }
131
133
  }
@@ -135,48 +137,20 @@ export function analyzeDataAccess(ast: Program, schema: Schema, validationContex
135
137
  return analysisReport;
136
138
  }
137
139
 
138
- function findArrowFunctionContext(
140
+ function findEnclosingArrowContext(
139
141
  targetNode: any,
140
- ast: Program,
141
142
  arrowFunctionContexts: Map<any, Map<string, any>>
142
143
  ): 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
- }
144
+ let best: { arrowFn: any; size: number } | null = null;
145
+ for (const arrowFn of arrowFunctionContexts.keys()) {
146
+ if (targetNode.start >= arrowFn.start && targetNode.end <= arrowFn.end) {
147
+ const size = arrowFn.end - arrowFn.start;
148
+ if (!best || size < best.size) {
149
+ best = { arrowFn, size };
173
150
  }
174
151
  }
175
- return null;
176
152
  }
177
-
178
- const result = findInNode(ast);
179
- return result?.arrowFunction ? arrowFunctionContexts.get(result.arrowFunction) || null : null;
153
+ return best ? arrowFunctionContexts.get(best.arrowFn) || null : null;
180
154
  }
181
155
 
182
156
  function getArrayElementTypeFromCall(
@@ -185,15 +159,16 @@ function getArrayElementTypeFromCall(
185
159
  arrowFunctionContexts: Map<any, Map<string, any>>
186
160
  ): any {
187
161
  const { callee } = node;
162
+ const dataKeys = new Set(Object.keys(schema.data ?? {}));
188
163
 
189
164
  if (callee.type !== "MemberExpression") {
190
165
  return null;
191
166
  }
192
167
 
193
- if (isSimpleDataAccess(callee)) {
168
+ if (isSimpleDataAccess(callee, dataKeys)) {
194
169
  const path = extractPath(callee);
195
- if (path[0] === "data") {
196
- const schemaPath = path.slice(1, -1); // Remove "data" prefix and method name
170
+ if (dataKeys.has(path[0])) {
171
+ const schemaPath = path.slice(0, -1); // Remove method name
197
172
  const arraySchema = getSchemaAtPath(schemaPath, schema);
198
173
 
199
174
  if (arraySchema?.type === "array") {
@@ -219,14 +194,6 @@ function getArrayElementTypeFromCall(
219
194
  return getArrayElementType(propertySchema);
220
195
  }
221
196
  }
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
197
  }
231
198
  }
232
199
 
@@ -258,40 +225,6 @@ function isNodeInsideArrowFunction(targetNode: any, arrowFunction: any): boolean
258
225
  return false;
259
226
  }
260
227
 
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
228
  function getSchemaAtPath(path: string[], schema: Schema): any {
296
229
  if (!schema.data || path.length === 0) {
297
230
  return schema.data;
@@ -347,3 +280,25 @@ function validateParameterAccess(
347
280
  export function getArrayElementType(schema: ArrayPropertySchema): PropertySchema | null {
348
281
  return schema.shape || null;
349
282
  }
283
+
284
+ function buildParentMap(ast: Program): Map<any, any> {
285
+ const map = new Map<any, any>();
286
+ function visit(node: any, parent: any): void {
287
+ if (!node || typeof node !== "object" || !node.type) return;
288
+ map.set(node, parent);
289
+ for (const key of Object.keys(node)) {
290
+ const value = node[key];
291
+ if (value && typeof value === "object") {
292
+ if (Array.isArray(value)) {
293
+ for (const child of value) {
294
+ visit(child, node);
295
+ }
296
+ } else {
297
+ visit(value, node);
298
+ }
299
+ }
300
+ }
301
+ }
302
+ visit(ast, null);
303
+ return map;
304
+ }
@@ -9,6 +9,7 @@ import { getNodeRange } from "../utils.js";
9
9
 
10
10
  export function analyzeMethodCalls(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
11
11
  const analysisReport = new AnalysisReport();
12
+ const dataKeys = new Set(Object.keys(schema.data ?? {}));
12
13
 
13
14
  traverse(ast, {
14
15
  CallExpression(node) {
@@ -17,10 +18,10 @@ export function analyzeMethodCalls(ast: Program, schema: Schema, validationConte
17
18
  if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
18
19
  const method = callee.property.name;
19
20
 
20
- if (isSimpleDataAccess(callee)) {
21
+ if (isSimpleDataAccess(callee, dataKeys)) {
21
22
  const path = extractPath(callee);
22
- if (path[0] === "data") {
23
- const schemaPath = path.slice(1, -1);
23
+ if (dataKeys.has(path[0])) {
24
+ const schemaPath = path.slice(0, -1);
24
25
  if (schemaPath.length > 0) {
25
26
  validateSchemaPath(schemaPath, schema, analysisReport, callee, validationContext);
26
27
  }
@@ -1,4 +1,4 @@
1
- import type { MemberExpression, Program } from "acorn";
1
+ import type { MemberExpression } from "acorn";
2
2
  import {
3
3
  isAllowedOnArray,
4
4
  isAllowedOnString,
@@ -10,7 +10,7 @@ import { AnalysisReport } from "../analysis-report.js";
10
10
  import { getNodeRange } from "../utils.js";
11
11
  import { ValidationContext } from "../validation-context.js";
12
12
 
13
- export function isSimpleDataAccess(node: MemberExpression): boolean {
13
+ export function isSimpleDataAccess(node: MemberExpression, dataKeys: ReadonlySet<string>): boolean {
14
14
  if (node.computed) {
15
15
  return node.property.type === "Literal" && typeof node.property.value === "number";
16
16
  }
@@ -22,7 +22,7 @@ export function isSimpleDataAccess(node: MemberExpression): boolean {
22
22
 
23
23
  while (current) {
24
24
  if (current.type === "Identifier") {
25
- return current.name === "data";
25
+ return dataKeys.has(current.name);
26
26
  }
27
27
 
28
28
  if (current.type === "MemberExpression") {
@@ -70,35 +70,6 @@ export function extractPath(node: MemberExpression): string[] {
70
70
  return path;
71
71
  }
72
72
 
73
- export function getParentNode(targetNode: any, ast: Program): any | null {
74
- let parent: any = null;
75
-
76
- function traverseForParent(node: any, currentParent: any = null): void {
77
- if (node === targetNode) {
78
- parent = currentParent;
79
- return;
80
- }
81
-
82
- for (const key of Object.keys(node)) {
83
- const value = node[key];
84
- if (value && typeof value === "object") {
85
- if (Array.isArray(value)) {
86
- value.forEach((child) => {
87
- if (child && typeof child === "object" && child.type) {
88
- traverseForParent(child, node);
89
- }
90
- });
91
- } else if (value.type) {
92
- traverseForParent(value, node);
93
- }
94
- }
95
- }
96
- }
97
-
98
- traverseForParent(ast);
99
- return parent;
100
- }
101
-
102
73
  export function getAvailablePropsAtPath(path: string[], depth: number, schemaData: any): string[] {
103
74
  if (!schemaData || depth < 0) {
104
75
  return schemaData ? Object.keys(schemaData) : [];
@@ -148,7 +119,7 @@ export function validateSchemaPath(
148
119
  if (!current) {
149
120
  analysisReport.addIssue(
150
121
  "INVALID_DATA_ACCESS",
151
- `Property 'data.${path[0]}' does not exist in schema`,
122
+ `Property '${path[0]}' does not exist in schema`,
152
123
  getNodeRange(node),
153
124
  validationContext.getSnapshot(),
154
125
  Object.keys(schemaData)
@@ -160,7 +131,7 @@ export function validateSchemaPath(
160
131
 
161
132
  for (let i = 1; i < path.length; i++) {
162
133
  const prop = path[i];
163
- const isElementAccess = elementAccessFlags[i];
134
+ const isElementAccess = elementAccessFlags[i - 1];
164
135
 
165
136
  if (current.type === "array") {
166
137
  if (!isElementAccess) {
@@ -168,7 +139,7 @@ export function validateSchemaPath(
168
139
  return;
169
140
  }
170
141
 
171
- const accessedPath = ["data", ...path.slice(0, i), prop].join(".");
142
+ const accessedPath = [...path.slice(0, i), prop].join(".");
172
143
  analysisReport.addIssue(
173
144
  "INVALID_DATA_ACCESS",
174
145
  `Property '${accessedPath}' does not exist on array type`,
@@ -201,7 +172,7 @@ export function validateSchemaPath(
201
172
 
202
173
  analysisReport.addIssue(
203
174
  "INVALID_DATA_ACCESS",
204
- `Property 'data.${paths.join(".")}' does not exist in schema`,
175
+ `Property '${paths.join(".")}' does not exist in schema`,
205
176
  getNodeRange(node),
206
177
  validationContext.getSnapshot(),
207
178
  suggestions
@@ -218,7 +189,7 @@ export function validateSchemaPath(
218
189
  return;
219
190
  }
220
191
 
221
- const accessedPath = ["data", ...path.slice(0, i), prop].join(".");
192
+ const accessedPath = [...path.slice(0, i), prop].join(".");
222
193
  analysisReport.addIssue(
223
194
  "INVALID_DATA_ACCESS",
224
195
  `Property '${accessedPath}' does not exist on array type`,
@@ -234,7 +205,7 @@ export function validateSchemaPath(
234
205
  return;
235
206
  }
236
207
 
237
- const accessedPath = ["data", ...path.slice(0, i), prop].join(".");
208
+ const accessedPath = [...path.slice(0, i), prop].join(".");
238
209
  analysisReport.addIssue(
239
210
  "INVALID_DATA_ACCESS",
240
211
  `Property '${accessedPath}' does not exist on string type`,
@@ -259,7 +230,7 @@ export function validateSchemaPath(
259
230
  return;
260
231
  }
261
232
 
262
- const accessedPath = ["data", ...path.slice(0, i), prop].join(".");
233
+ const accessedPath = [...path.slice(0, i), prop].join(".");
263
234
  analysisReport.addIssue(
264
235
  "INVALID_DATA_ACCESS",
265
236
  `Property '${accessedPath}' does not exist on string type`,
@@ -288,7 +259,7 @@ export function validateSchemaPath(
288
259
 
289
260
  analysisReport.addIssue(
290
261
  "INVALID_DATA_ACCESS",
291
- `Property 'data.${paths.join(".")}' does not exist in schema`,
262
+ `Property '${paths.join(".")}' does not exist in schema`,
292
263
  getNodeRange(node),
293
264
  validationContext.getSnapshot(),
294
265
  availableProps
@@ -1,18 +1,17 @@
1
1
  import type { Program } from "acorn";
2
- import { traverse } from "../../traverse.js";
2
+ import { isJsxAttribute, isJsxExpressionContainer, type JSXAttribute } from "../../jsx.js";
3
3
  import {
4
- type Schema,
5
- isAttributeAllowed,
6
4
  getAllowedAttributes,
7
- getRequiredAttributes,
8
5
  getAttributeSchema,
9
6
  getEnumValues,
7
+ getRequiredAttributes,
8
+ isAttributeAllowed,
9
+ type Schema,
10
10
  } from "../../schema.js";
11
- import { isJsxAttribute, isJsxExpressionContainer, type JSXAttribute } from "../../jsx.js";
12
- import { ValidationContext } from "../validation-context.js";
11
+ import { traverse } from "../../traverse.js";
13
12
  import { AnalysisReport } from "../analysis-report.js";
14
-
15
- import { getNodeRange, getAttributeSimilarityMatchers, getBestSimilarityMatcherSuggestion } from "../utils.js";
13
+ import { getAttributeSimilarityMatchers, getBestSimilarityMatcherSuggestion, getNodeRange } from "../utils.js";
14
+ import type { ValidationContext } from "../validation-context.js";
16
15
 
17
16
  export function analyzeElementAttributes(
18
17
  ast: Program,
@@ -21,11 +20,24 @@ export function analyzeElementAttributes(
21
20
  ): AnalysisReport {
22
21
  const analysisReport = new AnalysisReport();
23
22
 
23
+ const localFunctionNames = new Set<string>();
24
+ for (const stmt of ast.body) {
25
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
26
+ localFunctionNames.add(stmt.id.name);
27
+ }
28
+ }
29
+
24
30
  traverse(ast, {
25
31
  JSXElement(node) {
26
32
  const tagName = node.openingElement.name.name;
27
33
 
28
34
  validationContext.enterElement(tagName);
35
+
36
+ if (localFunctionNames.has(tagName)) {
37
+ validationContext.exitElement();
38
+ return;
39
+ }
40
+
29
41
  const currentContext = validationContext.getSnapshot();
30
42
 
31
43
  const allowedAttributes = getAllowedAttributes(schema, tagName);
@@ -132,7 +144,10 @@ function getAttributeLiteralValue(attribute: JSXAttribute): string | number | bo
132
144
  }
133
145
 
134
146
  if (isJsxExpressionContainer(attribute.value)) {
135
- const expression = attribute.value.expression as { type?: string; value?: unknown };
147
+ const expression = attribute.value.expression as {
148
+ type?: string;
149
+ value?: unknown;
150
+ };
136
151
 
137
152
  if (expression?.type === "Literal") {
138
153
  return expression.value as string | number | boolean | null | undefined;
@@ -1,10 +1,10 @@
1
1
  import type { Program } from "acorn";
2
+ import { isJsxElement, isJsxFragment, isJsxLeafNode, isJsxText } from "../../jsx.js";
3
+ import { getAllowedChildren, isSelfClosing, type Schema } from "../../schema.js";
2
4
  import { traverse } from "../../traverse.js";
3
- import { type Schema, getAllowedChildren, isSelfClosing } from "../../schema.js";
4
- import { isJsxElement, isJsxFragment, isJsxText, isJsxLeafNode } from "../../jsx.js";
5
- import { ValidationContext } from "../validation-context.js";
6
5
  import { AnalysisReport } from "../analysis-report.js";
7
- import { getNodeRange, getElementSimilarityMatchers, getBestSimilarityMatcherSuggestion } from "../utils.js";
6
+ import { getBestSimilarityMatcherSuggestion, getElementSimilarityMatchers, getNodeRange } from "../utils.js";
7
+ import type { ValidationContext } from "../validation-context.js";
8
8
 
9
9
  export function analyzeElementChildren(
10
10
  ast: Program,
@@ -13,12 +13,24 @@ export function analyzeElementChildren(
13
13
  ): AnalysisReport {
14
14
  const analysisReport = new AnalysisReport();
15
15
 
16
+ const localFunctionNames = new Set<string>();
17
+ for (const stmt of ast.body) {
18
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
19
+ localFunctionNames.add(stmt.id.name);
20
+ }
21
+ }
22
+
16
23
  traverse(ast, {
17
24
  JSXElement(node) {
18
25
  const parentTag = node.openingElement.name.name;
19
26
 
20
27
  validationContext.enterElement(parentTag);
21
28
 
29
+ if (localFunctionNames.has(parentTag)) {
30
+ validationContext.exitElement();
31
+ return;
32
+ }
33
+
22
34
  const currentContext = validationContext.getSnapshot();
23
35
 
24
36
  const allowedChildren = getAllowedChildren(schema, parentTag);
@@ -57,6 +69,10 @@ export function analyzeElementChildren(
57
69
  if (isJsxElement(child)) {
58
70
  const childTag = child.openingElement.name.name;
59
71
 
72
+ if (localFunctionNames.has(childTag)) {
73
+ continue;
74
+ }
75
+
60
76
  if (!allowedChildren.includes(childTag)) {
61
77
  const elementSimilarityMatchers = getElementSimilarityMatchers(childTag, allowedChildren);
62
78
  const bestSimilarityMatcherSuggestion = getBestSimilarityMatcherSuggestion(childTag, allowedChildren);
@@ -83,8 +99,6 @@ export function analyzeElementChildren(
83
99
  } else if (isJsxText(child)) {
84
100
  const hasContent = child.value.trim().length > 0;
85
101
  if (!hasContent) {
86
- // Allow whitespace-only text nodes (very common in JSX formatting)
87
- continue;
88
102
  }
89
103
  }
90
104
  }
@@ -1,19 +1,31 @@
1
1
  import type { Program } from "acorn";
2
+ import { getAllElements, isElementAllowed, type Schema } from "../../schema.js";
2
3
  import { traverse } from "../../traverse.js";
3
- import { type Schema, isElementAllowed, getAllElements } from "../../schema.js";
4
- import { ValidationContext } from "../validation-context.js";
5
4
  import { AnalysisReport } from "../analysis-report.js";
6
- import { getNodeRange, getElementSimilarityMatchers, getBestSimilarityMatcherSuggestion } from "../utils.js";
5
+ import { getBestSimilarityMatcherSuggestion, getElementSimilarityMatchers, getNodeRange } from "../utils.js";
6
+ import type { ValidationContext } from "../validation-context.js";
7
7
 
8
8
  export function analyzeElementTags(ast: Program, schema: Schema, validationContext: ValidationContext): AnalysisReport {
9
9
  const analysisReport = new AnalysisReport();
10
10
 
11
+ const localFunctionNames = new Set<string>();
12
+ for (const stmt of ast.body) {
13
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
14
+ localFunctionNames.add(stmt.id.name);
15
+ }
16
+ }
17
+
11
18
  traverse(ast, {
12
19
  JSXElement(node) {
13
20
  const tagName = node.openingElement.name.name;
14
21
 
15
22
  validationContext.enterElement(tagName);
16
23
 
24
+ if (localFunctionNames.has(tagName)) {
25
+ validationContext.exitElement();
26
+ return;
27
+ }
28
+
17
29
  const snapshot = validationContext.getSnapshot();
18
30
  const range = getNodeRange(node);
19
31
 
@@ -6,16 +6,14 @@ import { analyzeCallExpressions } from "./call-expressions.js";
6
6
  import { analyzeDeclarations } from "./declarations.js";
7
7
  import { analyzeExpressions } from "./expressions.js";
8
8
  import { analyzeIdentifiers } from "./identifiers.js";
9
- import { analyzeMemberAccess } from "./member-access.js";
10
9
 
11
10
  export function analyzeSecurity(ast: Program, schema: Schema): AnalysisReport {
12
11
  const validationContext = new ValidationContext();
13
12
 
14
13
  return new AnalysisReport().merge(
15
14
  analyzeDeclarations(ast, schema, validationContext),
16
- //analyzeIdentifiers(ast, schema, validationContext),
15
+ analyzeIdentifiers(ast, schema, validationContext),
17
16
  analyzeExpressions(ast, schema, validationContext),
18
- analyzeMemberAccess(ast, schema, validationContext),
19
17
  analyzeCallExpressions(ast, schema, validationContext)
20
18
  );
21
19
  }
@@ -1,39 +1,59 @@
1
1
  import type { Program } from "acorn";
2
- import { traverse } from "../../traverse.js";
2
+ import { getBuiltinGlobals } from "../../builtins.js";
3
3
  import type { Schema } from "../../schema.js";
4
- import { ValidationContext } from "../validation-context.js";
4
+ import { traverse } from "../../traverse.js";
5
5
  import { AnalysisReport } from "../analysis-report.js";
6
6
  import { getNodeRange } from "../utils.js";
7
+ import type { ValidationContext } from "../validation-context.js";
7
8
 
8
9
  export function analyzeCallExpressions(
9
10
  ast: Program,
10
- _schema: Schema,
11
+ schema: Schema,
11
12
  validationContext: ValidationContext
12
13
  ): AnalysisReport {
13
14
  const analysisReport = new AnalysisReport();
14
15
 
16
+ const localFunctionNames = new Set<string>();
17
+ for (const stmt of ast.body) {
18
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
19
+ localFunctionNames.add(stmt.id.name);
20
+ }
21
+ }
22
+ const schemaFunctionNames = new Set(Object.keys(schema.functions ?? {}));
23
+ const builtinGlobals = new Set(getBuiltinGlobals());
24
+
15
25
  traverse(ast, {
16
26
  CallExpression(node) {
17
27
  const { callee } = node;
18
28
 
19
- if (callee.type !== "MemberExpression") {
20
- analysisReport.addIssue(
21
- "DIRECT_CALL_NOT_ALLOWED",
22
- "only member calls allowed",
23
- getNodeRange(node),
24
- validationContext.getSnapshot()
25
- );
29
+ if (callee.type === "MemberExpression") {
30
+ if (callee.property.type !== "Identifier" || callee.computed) {
31
+ analysisReport.addIssue(
32
+ "DYNAMIC_METHOD_NOT_ALLOWED",
33
+ "only explicitly allowed methods are permitted",
34
+ getNodeRange(callee),
35
+ validationContext.getSnapshot()
36
+ );
37
+ }
26
38
  return;
27
39
  }
28
40
 
29
- if (callee.property.type !== "Identifier" || callee.computed) {
30
- analysisReport.addIssue(
31
- "DYNAMIC_METHOD_NOT_ALLOWED",
32
- "only explicitly allowed methods are permitted",
33
- getNodeRange(callee),
34
- validationContext.getSnapshot()
35
- );
41
+ if (callee.type === "Identifier") {
42
+ if (
43
+ localFunctionNames.has(callee.name) ||
44
+ schemaFunctionNames.has(callee.name) ||
45
+ builtinGlobals.has(callee.name)
46
+ ) {
47
+ return;
48
+ }
36
49
  }
50
+
51
+ analysisReport.addIssue(
52
+ "DIRECT_CALL_NOT_ALLOWED",
53
+ "only member calls allowed",
54
+ getNodeRange(node),
55
+ validationContext.getSnapshot()
56
+ );
37
57
  },
38
58
  });
39
59