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
@@ -0,0 +1,41 @@
1
+ import type { Program } from "acorn";
2
+ import { traverse } from "../../traverse";
3
+ import type { Schema } from "../../schema";
4
+ import { ValidationContext } from "../validation-context";
5
+ import { AnalysisReport } from "../analysis-report";
6
+ import { getNodeRange } from "../utils";
7
+
8
+ export function analyzeCallExpressions(
9
+ ast: Program,
10
+ _schema: Schema,
11
+ validationContext: ValidationContext
12
+ ): AnalysisReport {
13
+ const analysisReport = new AnalysisReport();
14
+
15
+ traverse(ast, {
16
+ CallExpression(node) {
17
+ const { callee } = node;
18
+
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
+ );
26
+ return;
27
+ }
28
+
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
+ );
36
+ }
37
+ },
38
+ });
39
+
40
+ return analysisReport;
41
+ }
@@ -0,0 +1,67 @@
1
+ import type { Program } from "acorn";
2
+ import { traverse } from "../../traverse";
3
+ import type { Schema } from "../../schema";
4
+ import { ValidationContext } from "../validation-context";
5
+ import { AnalysisReport } from "../analysis-report";
6
+ import { getNodeRange } from "../utils";
7
+
8
+ export function analyzeDeclarations(
9
+ ast: Program,
10
+ _schema: Schema,
11
+ validationContext: ValidationContext
12
+ ): AnalysisReport {
13
+ const analysisReport = new AnalysisReport();
14
+
15
+ traverse(ast, {
16
+ ImportDeclaration(node) {
17
+ analysisReport.addIssue(
18
+ "IMPORT_NOT_ALLOWED",
19
+ "import not allowed",
20
+ getNodeRange(node),
21
+ validationContext.getSnapshot()
22
+ );
23
+ },
24
+ ExportNamedDeclaration(node) {
25
+ analysisReport.addIssue(
26
+ "EXPORT_NOT_ALLOWED",
27
+ "export not allowed",
28
+ getNodeRange(node),
29
+ validationContext.getSnapshot()
30
+ );
31
+ },
32
+ ExportDefaultDeclaration(node) {
33
+ analysisReport.addIssue(
34
+ "EXPORT_NOT_ALLOWED",
35
+ "export not allowed",
36
+ getNodeRange(node),
37
+ validationContext.getSnapshot()
38
+ );
39
+ },
40
+ FunctionDeclaration(node) {
41
+ analysisReport.addIssue(
42
+ "FUNCTION_NOT_ALLOWED",
43
+ "function declarations not allowed",
44
+ getNodeRange(node),
45
+ validationContext.getSnapshot()
46
+ );
47
+ },
48
+ ClassDeclaration(node) {
49
+ analysisReport.addIssue(
50
+ "CLASS_NOT_ALLOWED",
51
+ "classes not allowed",
52
+ getNodeRange(node),
53
+ validationContext.getSnapshot()
54
+ );
55
+ },
56
+ VariableDeclaration(node) {
57
+ analysisReport.addIssue(
58
+ "VARIABLE_NOT_ALLOWED",
59
+ "variables not allowed",
60
+ getNodeRange(node),
61
+ validationContext.getSnapshot()
62
+ );
63
+ },
64
+ });
65
+
66
+ return analysisReport;
67
+ }
@@ -0,0 +1,100 @@
1
+ import type { Program } from "acorn";
2
+ import { traverse } from "../../traverse";
3
+ import type { Schema } from "../../schema";
4
+ import { ValidationContext } from "../validation-context";
5
+ import { AnalysisReport } from "../analysis-report";
6
+ import { getNodeRange } from "../utils";
7
+
8
+ export function analyzeExpressions(
9
+ ast: Program,
10
+ _schema: Schema,
11
+ validationContext: ValidationContext
12
+ ): AnalysisReport {
13
+ const analysisReport = new AnalysisReport();
14
+
15
+ traverse(ast, {
16
+ NewExpression(node) {
17
+ analysisReport.addIssue(
18
+ "NEW_NOT_ALLOWED",
19
+ "new not allowed",
20
+ getNodeRange(node),
21
+ validationContext.getSnapshot()
22
+ );
23
+ },
24
+ ThisExpression(node) {
25
+ analysisReport.addIssue(
26
+ "THIS_NOT_ALLOWED",
27
+ "this not allowed",
28
+ getNodeRange(node),
29
+ validationContext.getSnapshot()
30
+ );
31
+ },
32
+ WithStatement(node) {
33
+ analysisReport.addIssue(
34
+ "WITH_NOT_ALLOWED",
35
+ "with not allowed",
36
+ getNodeRange(node),
37
+ validationContext.getSnapshot()
38
+ );
39
+ },
40
+ AwaitExpression(node) {
41
+ analysisReport.addIssue(
42
+ "AWAIT_NOT_ALLOWED",
43
+ "await not allowed",
44
+ getNodeRange(node),
45
+ validationContext.getSnapshot()
46
+ );
47
+ },
48
+ YieldExpression(node) {
49
+ analysisReport.addIssue(
50
+ "YIELD_NOT_ALLOWED",
51
+ "yield not allowed",
52
+ getNodeRange(node),
53
+ validationContext.getSnapshot()
54
+ );
55
+ },
56
+ ArrowFunctionExpression(node) {
57
+ if (node.body.type === "BlockStatement") {
58
+ analysisReport.addIssue(
59
+ "ARROW_FUNCTION_BLOCK_BODY_NOT_ALLOWED",
60
+ "arrow functions with block body not allowed",
61
+ getNodeRange(node),
62
+ validationContext.getSnapshot()
63
+ );
64
+ }
65
+ },
66
+ ObjectExpression(node) {
67
+ for (const property of node.properties) {
68
+ if (property.type === "Property" && property.computed) {
69
+ analysisReport.addIssue(
70
+ "COMPUTED_PROPERTY_NOT_ALLOWED",
71
+ "computed object properties not allowed",
72
+ getNodeRange(property),
73
+ validationContext.getSnapshot()
74
+ );
75
+ }
76
+ }
77
+ },
78
+ MemberExpression(node) {
79
+ if (node.computed) {
80
+ // We allow numeric literals for array access (data.items[0])
81
+ if (node.property.type === "Literal" && typeof node.property.value === "number") {
82
+ return; // Allow numeric array indices
83
+ }
84
+
85
+ analysisReport.addIssue(
86
+ "COMPUTED_ACCESS_NOT_ALLOWED",
87
+ "computed member access not allowed",
88
+ getNodeRange(node),
89
+ validationContext.getSnapshot(),
90
+ [
91
+ "Use dot notation: obj.prop instead of obj[prop]",
92
+ "For arrays, use numeric indices: array[0] instead of array[variable]",
93
+ ]
94
+ );
95
+ }
96
+ },
97
+ });
98
+
99
+ return analysisReport;
100
+ }
@@ -0,0 +1,113 @@
1
+ import type { AnyNode, ArrowFunctionExpression, Identifier, Program } from "acorn";
2
+ import { traverse } from "../../traverse";
3
+ import type { Schema } from "../../schema";
4
+ import { getBuiltinGlobals } from "../../builtins";
5
+ import { ValidationContext } from "../validation-context";
6
+ import { AnalysisReport } from "../analysis-report";
7
+ import { getNodeRange } from "../utils";
8
+
9
+ export function analyzeIdentifiers(
10
+ ast: Program,
11
+ _schema: Schema,
12
+ validationContext: ValidationContext
13
+ ): AnalysisReport {
14
+ const analysisReport = new AnalysisReport();
15
+ const arrowParamScopes = getArrowParamScopes(ast);
16
+
17
+ traverse(ast, {
18
+ Identifier(node: Identifier) {
19
+ const { name } = node;
20
+
21
+ const allowedRoots = ["props", "data", ...getBuiltinGlobals()];
22
+
23
+ if (allowedRoots.includes(name)) {
24
+ return;
25
+ }
26
+
27
+ const arrowParent = findArrowParent(node, ast);
28
+
29
+ if (arrowParent) {
30
+ const params = arrowParamScopes.get(arrowParent);
31
+ if (params?.has(name)) {
32
+ return;
33
+ }
34
+ }
35
+
36
+ analysisReport.addIssue(
37
+ "IDENTIFIER_NOT_ALLOWED",
38
+ `Identifier "${name}" not allowed`,
39
+ getNodeRange(node),
40
+ validationContext.getSnapshot(),
41
+ allowedRoots
42
+ );
43
+ },
44
+ });
45
+
46
+ return analysisReport;
47
+ }
48
+
49
+ // Build a map of arrow functions to their parameter names so we can check if an
50
+ // identifier is a legit arrow function param or something shady. We do this in
51
+ // a separate traversal because it's cleaner than trying to track scope during the
52
+ // main validation pass.
53
+ function getArrowParamScopes(ast: Program): Map<ArrowFunctionExpression, Set<string>> {
54
+ const arrowParamScopes = new Map<ArrowFunctionExpression, Set<string>>();
55
+ traverse(ast, {
56
+ ArrowFunctionExpression(node) {
57
+ const { params } = node;
58
+ if (Array.isArray(params)) {
59
+ const paramNames = new Set<string>();
60
+ params.forEach((p) => {
61
+ if (p.type === "Identifier") {
62
+ paramNames.add(p.name);
63
+ }
64
+ });
65
+ arrowParamScopes.set(node, paramNames);
66
+ }
67
+ },
68
+ });
69
+ return arrowParamScopes;
70
+ }
71
+
72
+ // Walk up the AST tree to find the nearest ArrowFunctionExpression parent.
73
+ // This lets us check if an identifier is in scope as an arrow function parameter.
74
+ function findArrowParent(targetNode: AnyNode, rootNode: AnyNode): ArrowFunctionExpression | null {
75
+ let found: ArrowFunctionExpression | null = null;
76
+
77
+ function walk(node: AnyNode): boolean {
78
+ if (node === targetNode) {
79
+ return true;
80
+ }
81
+
82
+ const isArrow = node.type === "ArrowFunctionExpression";
83
+
84
+ if (isArrow) {
85
+ found = node as ArrowFunctionExpression;
86
+ }
87
+
88
+ for (const key of Object.keys(node)) {
89
+ const value = (node as any)[key];
90
+
91
+ if (value && typeof value === "object") {
92
+ if (Array.isArray(value)) {
93
+ for (const item of value) {
94
+ if (item && typeof item === "object" && walk(item)) {
95
+ return true;
96
+ }
97
+ }
98
+ } else if (walk(value)) {
99
+ return true;
100
+ }
101
+ }
102
+ }
103
+
104
+ if (isArrow) {
105
+ found = null;
106
+ }
107
+
108
+ return false;
109
+ }
110
+
111
+ walk(rootNode);
112
+ return found;
113
+ }
@@ -0,0 +1 @@
1
+ export { analyzeSecurity } from "./analyze";
@@ -0,0 +1,35 @@
1
+ import type { Program, Expression, Super } from "acorn";
2
+ import { traverse } from "../../traverse";
3
+ import type { Schema } from "../../schema";
4
+ import { ValidationContext } from "../validation-context";
5
+ import { AnalysisReport } from "../analysis-report";
6
+ import { getNodeRange } from "../utils";
7
+
8
+ export function analyzeMemberAccess(
9
+ ast: Program,
10
+ _schema: Schema,
11
+ validationContext: ValidationContext
12
+ ): AnalysisReport {
13
+ const analysisReport = new AnalysisReport();
14
+
15
+ traverse(ast, {
16
+ MemberExpression(node) {
17
+ let depth = 0;
18
+ let currentNode: Expression | Super = node;
19
+ while (currentNode.type === "MemberExpression") {
20
+ depth += 1;
21
+ currentNode = currentNode.object;
22
+ }
23
+ if (depth > 5) {
24
+ analysisReport.addIssue(
25
+ "MEMBER_CHAIN_TOO_DEEP",
26
+ "member chain too deep",
27
+ getNodeRange(node),
28
+ validationContext.getSnapshot()
29
+ );
30
+ }
31
+ },
32
+ });
33
+
34
+ return analysisReport;
35
+ }
@@ -0,0 +1,118 @@
1
+ export interface RangePosition {
2
+ line: number;
3
+ column: number;
4
+ }
5
+
6
+ export interface Range {
7
+ start: RangePosition;
8
+ end: RangePosition;
9
+ }
10
+
11
+ export function getNodeRange(node: any): Range {
12
+ if (node.loc) {
13
+ return {
14
+ start: {
15
+ line: node.loc.start.line - 1,
16
+ column: node.loc.start.column,
17
+ },
18
+ end: {
19
+ line: node.loc.end.line - 1,
20
+ column: node.loc.end.column,
21
+ },
22
+ };
23
+ }
24
+
25
+ return {
26
+ start: {
27
+ line: 0,
28
+ column: 0,
29
+ },
30
+ end: {
31
+ line: 0,
32
+ column: 0,
33
+ },
34
+ };
35
+ }
36
+
37
+ export function getElementSimilarityMatchers(invalid: string, available: string[]): SimilarityMatcher[] {
38
+ return getSimilarityMatchers(invalid, available, "element");
39
+ }
40
+
41
+ export function getAttributeSimilarityMatchers(invalid: string, available: string[]): SimilarityMatcher[] {
42
+ return getSimilarityMatchers(invalid, available, "attribute");
43
+ }
44
+
45
+ export function getBestSimilarityMatcherSuggestion(invalid: string, available: string[]): string | null {
46
+ const similarityMatchers = getElementSimilarityMatchers(invalid, available);
47
+
48
+ if (similarityMatchers.length === 0) {
49
+ return null;
50
+ }
51
+
52
+ const best = similarityMatchers[0];
53
+
54
+ if (best.confidence > 0.6) {
55
+ return best.suggestion;
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ type SimilarityMatcherType = "element" | "attribute" | "property" | "method";
62
+
63
+ interface SimilarityMatcher {
64
+ suggestion: string;
65
+ confidence: number;
66
+ type: SimilarityMatcherType;
67
+ }
68
+
69
+ function getSimilarityMatchers(invalid: string, available: string[], type: SimilarityMatcherType): SimilarityMatcher[] {
70
+ if (available.length === 0) {
71
+ return [];
72
+ }
73
+
74
+ return available
75
+ .map((suggestion) => ({
76
+ suggestion,
77
+ distance: levenshteinDistance(invalid.toLowerCase(), suggestion.toLowerCase()),
78
+ type,
79
+ }))
80
+ .filter((similarityMatcher) => similarityMatcher.distance <= Math.max(2, Math.floor(invalid.length * 0.4)))
81
+ .sort((a, b) => a.distance - b.distance)
82
+ .slice(0, 3)
83
+ .map((similarityMatcher) => ({
84
+ ...similarityMatcher,
85
+ confidence: 1 - similarityMatcher.distance / Math.max(invalid.length, similarityMatcher.suggestion.length),
86
+ }));
87
+ }
88
+
89
+ function levenshteinDistance(x: string, y: string): number {
90
+ if (x.length === 0) {
91
+ return y.length;
92
+ }
93
+ if (y.length === 0) {
94
+ return x.length;
95
+ }
96
+
97
+ const matrix: number[][] = [];
98
+
99
+ for (let i = 0; i <= y.length; i++) {
100
+ matrix[i] = [i];
101
+ }
102
+
103
+ for (let j = 0; j <= x.length; j++) {
104
+ matrix[0][j] = j;
105
+ }
106
+
107
+ for (let i = 1; i <= y.length; i++) {
108
+ for (let j = 1; j <= x.length; j++) {
109
+ if (y.charAt(i - 1) === x.charAt(j - 1)) {
110
+ matrix[i][j] = matrix[i - 1][j - 1];
111
+ } else {
112
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
113
+ }
114
+ }
115
+ }
116
+
117
+ return matrix[y.length][x.length];
118
+ }
@@ -0,0 +1,32 @@
1
+ export interface ValidationContextSnapshot {
2
+ elements: string[];
3
+ currentElement?: string;
4
+ parentElements: string[];
5
+ }
6
+
7
+ export class ValidationContext {
8
+ #elements: string[] = [];
9
+
10
+ get currentPath(): string {
11
+ if (this.#elements.length > 0) {
12
+ return this.#elements.join(" > ");
13
+ }
14
+ return "";
15
+ }
16
+
17
+ enterElement(element: string): void {
18
+ this.#elements.push(element);
19
+ }
20
+
21
+ exitElement(): void {
22
+ this.#elements.pop();
23
+ }
24
+
25
+ getSnapshot(): ValidationContextSnapshot {
26
+ return {
27
+ elements: this.#elements.slice(),
28
+ currentElement: this.#elements[this.#elements.length - 1],
29
+ parentElements: this.#elements.slice(0, -1),
30
+ };
31
+ }
32
+ }