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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +368 -0
  3. package/lib/analyze/analysis-error.d.ts.map +1 -0
  4. package/lib/analyze/analysis-report.d.ts +33 -0
  5. package/lib/analyze/analysis-report.d.ts.map +1 -0
  6. package/lib/analyze/analysis-report.js +194 -0
  7. package/lib/analyze/analysis-report.js.map +1 -0
  8. package/lib/analyze/analyze.d.ts.map +1 -0
  9. package/lib/analyze/analyze.test.d.ts.map +1 -0
  10. package/lib/analyze/analyze.test.js +765 -0
  11. package/lib/analyze/analyze.test.js.map +1 -0
  12. package/lib/analyze/data/analyze.d.ts.map +1 -0
  13. package/lib/analyze/data/data-access.d.ts.map +1 -0
  14. package/lib/analyze/data/index.d.ts.map +1 -0
  15. package/lib/analyze/data/method-calls.d.ts.map +1 -0
  16. package/lib/analyze/data/utils.d.ts.map +1 -0
  17. package/lib/analyze/index.d.ts.map +1 -0
  18. package/lib/analyze/jsx/analyze.d.ts.map +1 -0
  19. package/lib/analyze/jsx/element-attributes.d.ts.map +1 -0
  20. package/lib/analyze/jsx/element-attributes.js +89 -0
  21. package/lib/analyze/jsx/element-attributes.js.map +1 -0
  22. package/lib/analyze/jsx/element-children.d.ts.map +1 -0
  23. package/lib/analyze/jsx/element-tags.d.ts.map +1 -0
  24. package/lib/analyze/jsx/index.d.ts.map +1 -0
  25. package/lib/analyze/security/analyze.d.ts.map +1 -0
  26. package/lib/analyze/security/call-expressions.d.ts.map +1 -0
  27. package/lib/analyze/security/declarations.d.ts.map +1 -0
  28. package/lib/analyze/security/expressions.d.ts.map +1 -0
  29. package/lib/analyze/security/identifiers.d.ts.map +1 -0
  30. package/lib/analyze/security/index.d.ts.map +1 -0
  31. package/lib/analyze/security/member-access.d.ts.map +1 -0
  32. package/lib/builtins.d.ts +50 -0
  33. package/lib/builtins.d.ts.map +1 -0
  34. package/lib/builtins.js +1065 -0
  35. package/lib/builtins.js.map +1 -0
  36. package/lib/codegen/builtin-types.d.ts +11 -0
  37. package/lib/codegen/builtin-types.d.ts.map +1 -0
  38. package/lib/codegen/builtin-types.js +151 -0
  39. package/lib/codegen/builtin-types.js.map +1 -0
  40. package/lib/codegen/builtins/builtin-types.d.ts +2 -0
  41. package/lib/codegen/builtins/builtin-types.d.ts.map +1 -0
  42. package/lib/codegen/builtins/builtin-types.js +143 -0
  43. package/lib/codegen/builtins/builtin-types.js.map +1 -0
  44. package/lib/codegen/builtins/index.d.ts +2 -0
  45. package/lib/codegen/builtins/index.d.ts.map +1 -0
  46. package/lib/codegen/builtins/index.js +6 -0
  47. package/lib/codegen/builtins/index.js.map +1 -0
  48. package/lib/codegen/declare-types.d.ts +20 -0
  49. package/lib/codegen/declare-types.d.ts.map +1 -0
  50. package/lib/codegen/declare-types.js +299 -0
  51. package/lib/codegen/declare-types.js.map +1 -0
  52. package/lib/codegen/generate-typescript-definitions.d.ts +31 -0
  53. package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -0
  54. package/lib/codegen/generate-typescript-definitions.js +37 -0
  55. package/lib/codegen/generate-typescript-definitions.js.map +1 -0
  56. package/lib/codegen/generate-typescript-definitions.test.d.ts +2 -0
  57. package/lib/codegen/generate-typescript-definitions.test.d.ts.map +1 -0
  58. package/lib/codegen/generate-typescript-definitions.test.js +587 -0
  59. package/lib/codegen/generate-typescript-definitions.test.js.map +1 -0
  60. package/lib/codegen/index.d.ts +2 -0
  61. package/lib/codegen/index.d.ts.map +1 -0
  62. package/lib/codegen/index.js +6 -0
  63. package/lib/codegen/index.js.map +1 -0
  64. package/lib/codegen/schema/component-types.d.ts +3 -0
  65. package/lib/codegen/schema/component-types.d.ts.map +1 -0
  66. package/lib/codegen/schema/component-types.js +118 -0
  67. package/lib/codegen/schema/component-types.js.map +1 -0
  68. package/lib/codegen/schema/data-types.d.ts +3 -0
  69. package/lib/codegen/schema/data-types.d.ts.map +1 -0
  70. package/lib/codegen/schema/data-types.js +98 -0
  71. package/lib/codegen/schema/data-types.js.map +1 -0
  72. package/lib/codegen/schema/index.d.ts +3 -0
  73. package/lib/codegen/schema/index.d.ts.map +1 -0
  74. package/lib/codegen/schema/index.js +8 -0
  75. package/lib/codegen/schema/index.js.map +1 -0
  76. package/lib/codegen/schema/utils.d.ts +4 -0
  77. package/lib/codegen/schema/utils.d.ts.map +1 -0
  78. package/lib/codegen/schema/utils.js +103 -0
  79. package/lib/codegen/schema/utils.js.map +1 -0
  80. package/lib/codegen/schema-to-types.d.ts +14 -0
  81. package/lib/codegen/schema-to-types.d.ts.map +1 -0
  82. package/lib/codegen/schema-to-types.js +203 -0
  83. package/lib/codegen/schema-to-types.js.map +1 -0
  84. package/lib/compile/compile.d.ts.map +1 -0
  85. package/lib/compile/compile.js +245 -0
  86. package/lib/compile/compile.js.map +1 -0
  87. package/lib/compile/compile.test.d.ts.map +1 -0
  88. package/lib/compile/compile.test.js +148 -0
  89. package/lib/compile/compile.test.js.map +1 -0
  90. package/lib/evaluate/evaluate.d.ts +26 -0
  91. package/lib/evaluate/evaluate.d.ts.map +1 -0
  92. package/lib/evaluate/evaluate.js +40 -0
  93. package/lib/evaluate/evaluation-error.d.ts +4 -0
  94. package/lib/evaluate/evaluation-error.d.ts.map +1 -0
  95. package/lib/evaluate/evaluation-error.js +6 -0
  96. package/lib/index.d.ts +8 -0
  97. package/lib/index.d.ts.map +1 -0
  98. package/lib/index.js +16 -0
  99. package/lib/index.js.map +1 -0
  100. package/lib/render.d.ts +58 -0
  101. package/lib/render.d.ts.map +1 -0
  102. package/lib/render.js +52 -0
  103. package/lib/render.test.d.ts.map +1 -0
  104. package/lib/schema.d.ts +68 -0
  105. package/lib/schema.d.ts.map +1 -0
  106. package/lib/schema.js +106 -0
  107. package/lib/validate.d.ts +41 -0
  108. package/lib/validate.d.ts.map +1 -0
  109. package/lib/validate.js +42 -0
  110. package/package.json +27 -0
  111. package/src/analyze/analysis-error.ts +26 -0
  112. package/src/analyze/analysis-report.ts +228 -0
  113. package/src/analyze/analyze.test.ts +829 -0
  114. package/src/analyze/analyze.ts +10 -0
  115. package/src/analyze/data/analyze.ts +15 -0
  116. package/src/analyze/data/data-access.ts +349 -0
  117. package/src/analyze/data/index.ts +1 -0
  118. package/src/analyze/data/method-calls.ts +96 -0
  119. package/src/analyze/data/utils.ts +329 -0
  120. package/src/analyze/index.ts +3 -0
  121. package/src/analyze/jsx/analyze.ts +17 -0
  122. package/src/analyze/jsx/element-attributes.ts +143 -0
  123. package/src/analyze/jsx/element-children.ts +97 -0
  124. package/src/analyze/jsx/element-tags.ts +55 -0
  125. package/src/analyze/jsx/index.ts +1 -0
  126. package/src/analyze/security/analyze.ts +21 -0
  127. package/src/analyze/security/call-expressions.ts +41 -0
  128. package/src/analyze/security/declarations.ts +67 -0
  129. package/src/analyze/security/expressions.ts +100 -0
  130. package/src/analyze/security/identifiers.ts +113 -0
  131. package/src/analyze/security/index.ts +1 -0
  132. package/src/analyze/security/member-access.ts +35 -0
  133. package/src/analyze/utils.ts +118 -0
  134. package/src/analyze/validation-context.ts +32 -0
  135. package/src/builtins.ts +1149 -0
  136. package/src/codegen/builtins/builtin-types.ts +174 -0
  137. package/src/codegen/builtins/index.ts +1 -0
  138. package/src/codegen/generate-typescript-definitions.test.ts +676 -0
  139. package/src/codegen/generate-typescript-definitions.ts +35 -0
  140. package/src/codegen/index.ts +1 -0
  141. package/src/codegen/schema/component-types.ts +140 -0
  142. package/src/codegen/schema/data-types.ts +109 -0
  143. package/src/codegen/schema/index.ts +2 -0
  144. package/src/codegen/schema/utils.ts +111 -0
  145. package/src/compile/compilation-error.ts +37 -0
  146. package/src/compile/compile.test.ts +180 -0
  147. package/src/compile/compile.ts +282 -0
  148. package/src/compile/index.ts +2 -0
  149. package/src/evaluate/evaluate.test.ts +316 -0
  150. package/src/evaluate/evaluate.ts +81 -0
  151. package/src/evaluate/evaluation-error.ts +6 -0
  152. package/src/evaluate/index.ts +2 -0
  153. package/src/index.ts +10 -0
  154. package/src/jsx.ts +161 -0
  155. package/src/parse/index.ts +2 -0
  156. package/src/parse/parse-error.ts +14 -0
  157. package/src/parse/parse.test.ts +99 -0
  158. package/src/parse/parse.ts +40 -0
  159. package/src/render.test.ts +425 -0
  160. package/src/render.ts +75 -0
  161. package/src/schema.ts +195 -0
  162. package/src/traverse.test.ts +104 -0
  163. package/src/traverse.ts +78 -0
  164. package/src/types.ts +17 -0
  165. package/src/validate.ts +59 -0
package/lib/render.js ADDED
@@ -0,0 +1,52 @@
1
+ import { compile } from "./compile";
2
+ import { evaluate } from "./evaluate/evaluate";
3
+ import { parse } from "./parse";
4
+ import { analyze, AnalysisError } from "./analyze";
5
+ /**
6
+ * Renders a JSX expression with data validation and security constraints.
7
+ *
8
+ * Parses JSX source code, validates it against a schema for security and type safety,
9
+ * compiles it to executable code, and evaluates it with the provided data and components.
10
+ *
11
+ * @template T - The return type of the rendered JSX (e.g., ReactElement, VNode, string)
12
+ * @param source - JSX expression source code to render (e.g., `"<Text x={data.user.name}>Hello</Text>"`)
13
+ * @param schema - Schema defining allowed data properties, JSX elements, and hierarchy rules
14
+ * @param options - Rendering configuration
15
+ * @param options.data - Data object available as `data.*` in expressions
16
+ * @param options.components - Map of component names to component functions
17
+ * @param options.createElement - Function to create elements (e.g., React.createElement)
18
+ * @param options.minSeverity - Minimum severity (1=info, 2=warning, 3=error) required to abort rendering; defaults to `3`
19
+ * @returns The rendered JSX element of type T
20
+ *
21
+ * @throws {AnalysisError} When JSX contains security violations, invalid data access, or schema mismatches
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const schema: Schema = {
26
+ * data: {
27
+ * user: { type: "object", shape: { name: { type: "string" } } }
28
+ * },
29
+ * elements: {
30
+ * Text: { props: { children: { type: "string" } } }
31
+ * }
32
+ * };
33
+ *
34
+ * const result = render(
35
+ * "<Text>Hello {data.user.name}!</Text>",
36
+ * schema,
37
+ * {
38
+ * data: { user: { name: "World" } },
39
+ * components: { Text: ({ children }) => children },
40
+ * createElement: (type, props, ...children) => ({ type, props, children })
41
+ * }
42
+ * );
43
+ * ```
44
+ */
45
+ export function render(source, schema, { minSeverity = 3, ...options } = {}) {
46
+ const ast = parse(source);
47
+ const report = analyze(ast, schema);
48
+ if (report.hasIssues(minSeverity)) {
49
+ throw AnalysisError.fromReport(report);
50
+ }
51
+ return evaluate(compile(ast), options);
52
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.test.d.ts","sourceRoot":"","sources":["../src/render.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ export type StringPropertySchema = {
2
+ type: "string";
3
+ description?: string;
4
+ required?: boolean;
5
+ default?: string;
6
+ enum?: readonly string[];
7
+ };
8
+ export type NumberPropertySchema = {
9
+ type: "number";
10
+ description?: string;
11
+ required?: boolean;
12
+ default?: number;
13
+ enum?: readonly number[];
14
+ };
15
+ export type BooleanPropertySchema = {
16
+ type: "boolean";
17
+ description?: string;
18
+ required?: boolean;
19
+ default?: boolean;
20
+ enum?: readonly boolean[];
21
+ };
22
+ export type FunctionPropertySchema = {
23
+ type: "function";
24
+ description?: string;
25
+ required?: boolean;
26
+ };
27
+ export type ObjectPropertySchema = {
28
+ type: "object";
29
+ description?: string;
30
+ required?: boolean;
31
+ shape: Record<string, PropertySchema>;
32
+ };
33
+ export type ArrayPropertySchema = {
34
+ type: "array";
35
+ description?: string;
36
+ required?: boolean;
37
+ shape: PropertySchema;
38
+ };
39
+ export type PropertySchema = StringPropertySchema | NumberPropertySchema | BooleanPropertySchema | FunctionPropertySchema | ObjectPropertySchema | ArrayPropertySchema;
40
+ export type ElementSchema = {
41
+ description?: string;
42
+ props?: Record<string, PropertySchema>;
43
+ allowedChildren?: string[];
44
+ };
45
+ export interface Schema {
46
+ data?: Record<string, PropertySchema>;
47
+ elements?: Record<string, ElementSchema>;
48
+ }
49
+ export declare function isElementAllowed(schema: Schema, tagName: string): boolean;
50
+ export declare function getElementSchema(schema: Schema, tagName: string): ElementSchema | undefined;
51
+ export declare function getAllowedAttributes(schema: Schema, tagName: string): string[];
52
+ export declare function isAttributeAllowed(schema: Schema, tagName: string, attributeName: string): boolean;
53
+ export declare function getAllowedChildren(schema: Schema, tagName: string): string[] | undefined;
54
+ export declare function canHaveChildren(schema: Schema, tagName: string): boolean;
55
+ export declare function isSelfClosing(schema: Schema, tagName: string): boolean;
56
+ export declare function isChildAllowed(schema: Schema, parentTag: string, childTag: string): boolean;
57
+ export declare function getAllElements(schema: Schema): string[];
58
+ export declare function getElementConfig(schema: Schema, tagName: string): ElementSchema | undefined;
59
+ export declare function getAttributeSchema(schema: Schema, tagName: string, attributeName: string): PropertySchema | undefined;
60
+ export declare function getRequiredAttributes(schema: Schema, tagName: string): string[];
61
+ export declare function getOptionalAttributes(schema: Schema, tagName: string): string[];
62
+ export declare function isAttributeRequired(schema: Schema, tagName: string, attributeName: string): boolean;
63
+ export declare function getDataSchema(schema: Schema): Record<string, PropertySchema> | undefined;
64
+ export declare function getPropertyAtPath(schema: Schema, path: string[]): PropertySchema | undefined;
65
+ export declare function getAvailablePropertiesAtPath(schema: Schema, path: string[]): string[];
66
+ export type PropertyEnumValue = string | number | boolean;
67
+ export declare function getEnumValues(property: PropertySchema): readonly PropertyEnumValue[] | undefined;
68
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/lib/schema.js ADDED
@@ -0,0 +1,106 @@
1
+ export function isElementAllowed(schema, tagName) {
2
+ return !!schema.elements?.[tagName];
3
+ }
4
+ export function getElementSchema(schema, tagName) {
5
+ return schema.elements?.[tagName];
6
+ }
7
+ export function getAllowedAttributes(schema, tagName) {
8
+ const elementConfig = getElementSchema(schema, tagName);
9
+ return elementConfig?.props ? Object.keys(elementConfig.props) : [];
10
+ }
11
+ export function isAttributeAllowed(schema, tagName, attributeName) {
12
+ const allowedAttributes = getAllowedAttributes(schema, tagName);
13
+ return allowedAttributes.includes(attributeName);
14
+ }
15
+ export function getAllowedChildren(schema, tagName) {
16
+ const elementConfig = getElementSchema(schema, tagName);
17
+ return elementConfig?.allowedChildren;
18
+ }
19
+ export function canHaveChildren(schema, tagName) {
20
+ const allowedChildren = getAllowedChildren(schema, tagName);
21
+ return allowedChildren === undefined || allowedChildren.length > 0;
22
+ }
23
+ export function isSelfClosing(schema, tagName) {
24
+ const allowedChildren = getAllowedChildren(schema, tagName);
25
+ return allowedChildren !== undefined && allowedChildren.length === 0;
26
+ }
27
+ export function isChildAllowed(schema, parentTag, childTag) {
28
+ const allowedChildren = getAllowedChildren(schema, parentTag);
29
+ return allowedChildren === undefined || allowedChildren.includes(childTag);
30
+ }
31
+ // New schema utilities for enhanced validation
32
+ export function getAllElements(schema) {
33
+ return schema.elements ? Object.keys(schema.elements) : [];
34
+ }
35
+ export function getElementConfig(schema, tagName) {
36
+ return getElementSchema(schema, tagName);
37
+ }
38
+ export function getAttributeSchema(schema, tagName, attributeName) {
39
+ const elementSchema = getElementSchema(schema, tagName);
40
+ return elementSchema?.props?.[attributeName];
41
+ }
42
+ export function getRequiredAttributes(schema, tagName) {
43
+ const elementSchema = getElementSchema(schema, tagName);
44
+ if (!elementSchema?.props) {
45
+ return [];
46
+ }
47
+ const required = [];
48
+ for (const [attrName, attrSchema] of Object.entries(elementSchema.props)) {
49
+ if (attrSchema.required === true) {
50
+ required.push(attrName);
51
+ }
52
+ }
53
+ return required;
54
+ }
55
+ export function getOptionalAttributes(schema, tagName) {
56
+ const allAttributes = getAllowedAttributes(schema, tagName);
57
+ const requiredAttributes = getRequiredAttributes(schema, tagName);
58
+ return allAttributes.filter((attr) => !requiredAttributes.includes(attr));
59
+ }
60
+ export function isAttributeRequired(schema, tagName, attributeName) {
61
+ const requiredAttributes = getRequiredAttributes(schema, tagName);
62
+ return requiredAttributes.includes(attributeName);
63
+ }
64
+ export function getDataSchema(schema) {
65
+ return schema.data;
66
+ }
67
+ export function getPropertyAtPath(schema, path) {
68
+ if (!schema.data || path.length === 0) {
69
+ return undefined;
70
+ }
71
+ let current = schema.data[path[0]];
72
+ for (let i = 1; i < path.length && current; i++) {
73
+ if (current.type === "object" && current.shape) {
74
+ current = current.shape[path[i]];
75
+ }
76
+ else {
77
+ return undefined;
78
+ }
79
+ }
80
+ return current;
81
+ }
82
+ export function getAvailablePropertiesAtPath(schema, path) {
83
+ if (!schema.data) {
84
+ return [];
85
+ }
86
+ if (path.length === 0) {
87
+ return Object.keys(schema.data);
88
+ }
89
+ const parentProperty = getPropertyAtPath(schema, path);
90
+ if (parentProperty?.type === "object" && parentProperty.shape) {
91
+ return Object.keys(parentProperty.shape);
92
+ }
93
+ return [];
94
+ }
95
+ export function getEnumValues(property) {
96
+ switch (property.type) {
97
+ case "string":
98
+ return property.enum;
99
+ case "number":
100
+ return property.enum;
101
+ case "boolean":
102
+ return property.enum;
103
+ default:
104
+ return undefined;
105
+ }
106
+ }
@@ -0,0 +1,41 @@
1
+ import { AnalysisError } from "./analyze";
2
+ import { ParseError } from "./parse";
3
+ import { Schema } from "./schema";
4
+ export type ValidateResult = {
5
+ ok: true;
6
+ } | {
7
+ ok: false;
8
+ error: AnalysisError | ParseError;
9
+ };
10
+ export interface ValidateOptions {
11
+ /**
12
+ * Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error.
13
+ */
14
+ minSeverity?: 1 | 2 | 3;
15
+ }
16
+ /**
17
+ * Validates JSX expression syntax and schema compliance without execution.
18
+ *
19
+ * Performs static analysis to check if JSX source code is valid according to the schema.
20
+ * This includes syntax validation, security constraints, JSX element/attribute validation,
21
+ * and data access pattern validation - all without needing actual data or components.
22
+ *
23
+ * This function parses the source code and returns a simple ok/error result.
24
+ *
25
+ * @param source - JSX expression source code to validate
26
+ * @param schema - Schema defining allowed data properties, JSX elements, and hierarchy rules
27
+ * @returns Validation result indicating success or failure with parse or analysis error
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const result = validate("<Text>{data.user.name}</Text>", schema);
32
+ * if (result.ok) {
33
+ * console.log("JSX is valid!");
34
+ * } else {
35
+ * console.error("Validation failed with", result.report.errors.length, "errors");
36
+ * result.report.errors.forEach(err => console.error(err.message));
37
+ * }
38
+ * ```
39
+ */
40
+ export declare function validate(source: string, schema: Schema, { minSeverity }?: ValidateOptions): ValidateResult;
41
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,aAAa,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAS,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,GAAG,UAAU,CAAA;CAAE,CAAC;AAE7F,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CACzB;AACD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EAEd,EAAE,WAAe,EAAE,GAAE,eAAoB,GACxC,cAAc,CAiBhB"}
@@ -0,0 +1,42 @@
1
+ import { analyze, AnalysisError } from "./analyze";
2
+ import { parse, ParseError } from "./parse";
3
+ /**
4
+ * Validates JSX expression syntax and schema compliance without execution.
5
+ *
6
+ * Performs static analysis to check if JSX source code is valid according to the schema.
7
+ * This includes syntax validation, security constraints, JSX element/attribute validation,
8
+ * and data access pattern validation - all without needing actual data or components.
9
+ *
10
+ * This function parses the source code and returns a simple ok/error result.
11
+ *
12
+ * @param source - JSX expression source code to validate
13
+ * @param schema - Schema defining allowed data properties, JSX elements, and hierarchy rules
14
+ * @returns Validation result indicating success or failure with parse or analysis error
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const result = validate("<Text>{data.user.name}</Text>", schema);
19
+ * if (result.ok) {
20
+ * console.log("JSX is valid!");
21
+ * } else {
22
+ * console.error("Validation failed with", result.report.errors.length, "errors");
23
+ * result.report.errors.forEach(err => console.error(err.message));
24
+ * }
25
+ * ```
26
+ */
27
+ export function validate(source, schema, { minSeverity = 3 } = {}) {
28
+ try {
29
+ const ast = parse(source);
30
+ const report = analyze(ast, schema);
31
+ if (report.hasIssues(minSeverity)) {
32
+ return { ok: false, error: AnalysisError.fromReport(report) };
33
+ }
34
+ return { ok: true };
35
+ }
36
+ catch (error) {
37
+ if (error instanceof ParseError) {
38
+ return { ok: false, error };
39
+ }
40
+ throw error;
41
+ }
42
+ }
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "jsxpression",
3
+ "version": "0.1.0",
4
+ "description": "Validate and safely render JSX expressions",
5
+ "author": "Divid AB <info@divid.se>",
6
+ "repository": "https://github.com/dividab/abstract-visuals/tree/master/packages/jsxpression",
7
+ "license": "MIT",
8
+ "main": "lib/index.js",
9
+ "types": "lib/index",
10
+ "exports": {
11
+ ".": "./lib/index.js"
12
+ },
13
+ "files": [
14
+ "/lib",
15
+ "/src",
16
+ "README.md",
17
+ "package.json"
18
+ ],
19
+ "dependencies": {
20
+ "acorn": "8.15.0",
21
+ "acorn-jsx": "5.3.2",
22
+ "acorn-walk": "8.3.4"
23
+ },
24
+ "scripts": {
25
+ "test": "vitest run src/**/*.test.ts"
26
+ }
27
+ }
@@ -0,0 +1,26 @@
1
+ import { AnalysisReport } from "./analysis-report";
2
+
3
+ export class AnalysisError extends Error {
4
+ static fromReport(report: AnalysisReport): AnalysisError {
5
+ let message: string;
6
+ if (report.errors.length === 1) {
7
+ message = report.errors[0].message;
8
+ } else {
9
+ message = `Analysis failed (${report.errors.length} issues)`;
10
+ }
11
+
12
+ return new AnalysisError(message, report);
13
+ }
14
+
15
+ readonly report: AnalysisReport;
16
+
17
+ constructor(message: string, report: AnalysisReport) {
18
+ super(message);
19
+ this.name = "AnalysisError";
20
+ this.report = report;
21
+
22
+ if (Error.captureStackTrace) {
23
+ Error.captureStackTrace(this, AnalysisError);
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,228 @@
1
+ import { Range } from "./utils";
2
+ import { ValidationContextSnapshot } from "./validation-context";
3
+
4
+ type IssueSeverity = 1 | 2 | 3;
5
+
6
+ interface Issue {
7
+ code: IssueCode;
8
+ message: string;
9
+ severity: IssueSeverity;
10
+ custom: boolean;
11
+ range: Range;
12
+ suggestions: string[];
13
+ snapshot: ValidationContextSnapshot;
14
+ }
15
+
16
+ interface IssueDefinition {
17
+ severity: IssueSeverity;
18
+ custom: boolean;
19
+ }
20
+
21
+ const ISSUES_DEFINITIONS: Record<string, IssueDefinition> = {
22
+ VARIABLE_NOT_ALLOWED: {
23
+ severity: 3,
24
+ custom: true,
25
+ },
26
+ IDENTIFIER_NOT_ALLOWED: {
27
+ severity: 3,
28
+ custom: true,
29
+ },
30
+ IMPORT_NOT_ALLOWED: {
31
+ severity: 3,
32
+ custom: true,
33
+ },
34
+ EXPORT_NOT_ALLOWED: {
35
+ severity: 3,
36
+ custom: true,
37
+ },
38
+ ASSIGNMENT_NOT_ALLOWED: {
39
+ severity: 3,
40
+ custom: true,
41
+ },
42
+ UPDATE_NOT_ALLOWED: {
43
+ severity: 3,
44
+ custom: true,
45
+ },
46
+ DELETE_NOT_ALLOWED: {
47
+ severity: 3,
48
+ custom: true,
49
+ },
50
+ NEW_NOT_ALLOWED: {
51
+ severity: 3,
52
+ custom: true,
53
+ },
54
+ THIS_NOT_ALLOWED: {
55
+ severity: 3,
56
+ custom: true,
57
+ },
58
+ SUPER_NOT_ALLOWED: {
59
+ severity: 3,
60
+ custom: true,
61
+ },
62
+ YIELD_NOT_ALLOWED: {
63
+ severity: 3,
64
+ custom: true,
65
+ },
66
+ AWAIT_NOT_ALLOWED: {
67
+ severity: 3,
68
+ custom: true,
69
+ },
70
+ ARROW_FUNCTION_BLOCK_BODY_NOT_ALLOWED: {
71
+ severity: 3,
72
+ custom: true,
73
+ },
74
+ FUNCTION_NOT_ALLOWED: {
75
+ severity: 3,
76
+ custom: true,
77
+ },
78
+ CLASS_NOT_ALLOWED: {
79
+ severity: 3,
80
+ custom: true,
81
+ },
82
+ TRY_NOT_ALLOWED: {
83
+ severity: 3,
84
+ custom: true,
85
+ },
86
+ THROW_NOT_ALLOWED: {
87
+ severity: 3,
88
+ custom: true,
89
+ },
90
+ WITH_NOT_ALLOWED: {
91
+ severity: 3,
92
+ custom: true,
93
+ },
94
+ DEBUGGER_NOT_ALLOWED: {
95
+ severity: 3,
96
+ custom: true,
97
+ },
98
+ COMPUTED_ACCESS_NOT_ALLOWED: {
99
+ severity: 3,
100
+ custom: true,
101
+ },
102
+ COMPUTED_PROPERTY_NOT_ALLOWED: {
103
+ severity: 3,
104
+ custom: true,
105
+ },
106
+ DIRECT_CALL_NOT_ALLOWED: {
107
+ severity: 3,
108
+ custom: true,
109
+ },
110
+ DYNAMIC_METHOD_NOT_ALLOWED: {
111
+ severity: 3,
112
+ custom: true,
113
+ },
114
+ METHOD_NOT_ALLOWED: {
115
+ severity: 3,
116
+ custom: false,
117
+ },
118
+ INVALID_DATA_ACCESS: {
119
+ severity: 3,
120
+ custom: false,
121
+ },
122
+ INVALID_PARAMETER_ACCESS: {
123
+ severity: 3,
124
+ custom: false,
125
+ },
126
+ INVALID_ELEMENT: {
127
+ severity: 3,
128
+ custom: false,
129
+ },
130
+ INVALID_ATTRIBUTE: {
131
+ severity: 3,
132
+ custom: false,
133
+ },
134
+ MISSING_REQUIRED_ATTRIBUTE: {
135
+ severity: 3,
136
+ custom: false,
137
+ },
138
+ INVALID_CHILD_ELEMENT: {
139
+ severity: 3,
140
+ custom: true,
141
+ },
142
+ MEMBER_CHAIN_TOO_DEEP: {
143
+ severity: 2,
144
+ custom: true,
145
+ },
146
+ TOO_MANY_PARAMETERS: {
147
+ severity: 2,
148
+ custom: false,
149
+ },
150
+ INSUFFICIENT_PARAMETERS: {
151
+ severity: 2,
152
+ custom: false,
153
+ },
154
+ INVALID_ATTRIBUTE_VALUE: {
155
+ severity: 3,
156
+ custom: false,
157
+ },
158
+ SELF_CLOSING_WITH_CHILDREN: {
159
+ severity: 1,
160
+ custom: true,
161
+ },
162
+ } as const;
163
+
164
+ export type IssueCode = keyof typeof ISSUES_DEFINITIONS;
165
+
166
+ export class AnalysisReport {
167
+ readonly #issues: Issue[] = [];
168
+
169
+ get issues(): Issue[] {
170
+ return this.#issues.slice();
171
+ }
172
+
173
+ get errors(): Issue[] {
174
+ return this.#issues.filter((issue) => issue.severity === 3);
175
+ }
176
+
177
+ get warnings(): Issue[] {
178
+ return this.#issues.filter((issue) => issue.severity === 2);
179
+ }
180
+
181
+ get infos(): Issue[] {
182
+ return this.#issues.filter((issue) => issue.severity === 1);
183
+ }
184
+
185
+ get hasErrors(): boolean {
186
+ return this.errors.length > 0;
187
+ }
188
+
189
+ get hasWarnings(): boolean {
190
+ return this.warnings.length > 0;
191
+ }
192
+
193
+ get hasInfos(): boolean {
194
+ return this.infos.length > 0;
195
+ }
196
+
197
+ addIssue(
198
+ code: IssueCode,
199
+ message: string,
200
+ range: Range,
201
+ snapshot: ValidationContextSnapshot,
202
+ suggestions: string[] = []
203
+ ): void {
204
+ const { severity, custom } = ISSUES_DEFINITIONS[code];
205
+ this.#issues.push({
206
+ code,
207
+ message,
208
+ range,
209
+ snapshot,
210
+ suggestions,
211
+ severity,
212
+ custom,
213
+ });
214
+ }
215
+
216
+ hasIssues(minSeverity: IssueSeverity = 3): boolean {
217
+ return this.#issues.some((issue) => issue.severity >= minSeverity);
218
+ }
219
+
220
+ merge(...analysisReports: AnalysisReport[]): this {
221
+ for (const analysisReport of analysisReports) {
222
+ for (const issue of analysisReport.issues) {
223
+ this.addIssue(issue.code, issue.message, issue.range, issue.snapshot, issue.suggestions);
224
+ }
225
+ }
226
+ return this;
227
+ }
228
+ }