@player-ui/player 0.8.0--canary.307.9621 → 0.8.0-next.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 (214) hide show
  1. package/dist/Player.native.js +11630 -0
  2. package/dist/Player.native.js.map +1 -0
  3. package/dist/cjs/index.cjs +5626 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/{index.esm.js → index.legacy-esm.js} +2044 -1667
  6. package/dist/{index.cjs.js → index.mjs} +2052 -1761
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +29 -63
  9. package/src/__tests__/data.test.ts +498 -0
  10. package/src/__tests__/flow.test.ts +312 -0
  11. package/src/__tests__/helpers/action-exp.plugin.ts +22 -0
  12. package/src/__tests__/helpers/actions.flow.ts +67 -0
  13. package/src/__tests__/helpers/binding.plugin.ts +125 -0
  14. package/src/__tests__/helpers/expression.plugin.ts +88 -0
  15. package/src/__tests__/helpers/transform-plugin.ts +19 -0
  16. package/src/__tests__/helpers/validation.flow.ts +56 -0
  17. package/src/__tests__/player.test.ts +597 -0
  18. package/src/__tests__/string-resolver.test.ts +186 -0
  19. package/src/__tests__/validation.test.ts +3555 -0
  20. package/src/__tests__/view.test.ts +715 -0
  21. package/src/binding/__tests__/binding.test.ts +113 -0
  22. package/src/binding/__tests__/index.test.ts +208 -0
  23. package/src/binding/__tests__/resolver.test.ts +83 -0
  24. package/src/binding/binding.ts +6 -6
  25. package/src/binding/index.ts +34 -34
  26. package/src/binding/resolver.ts +19 -19
  27. package/src/binding/utils.ts +7 -7
  28. package/src/binding-grammar/__tests__/parser.test.ts +64 -0
  29. package/src/binding-grammar/__tests__/test-utils/ast-cases.ts +198 -0
  30. package/src/binding-grammar/__tests__/test-utils/perf-test.ts +66 -0
  31. package/src/binding-grammar/ast.ts +11 -11
  32. package/src/binding-grammar/custom/index.ts +19 -22
  33. package/src/binding-grammar/ebnf/index.ts +20 -21
  34. package/src/binding-grammar/ebnf/types.ts +13 -13
  35. package/src/binding-grammar/index.ts +4 -4
  36. package/src/binding-grammar/parsimmon/index.ts +14 -14
  37. package/src/controllers/constants/__tests__/index.test.ts +106 -0
  38. package/src/controllers/constants/index.ts +3 -3
  39. package/src/controllers/constants/utils.ts +4 -4
  40. package/src/controllers/data/controller.ts +22 -22
  41. package/src/controllers/data/index.ts +1 -1
  42. package/src/controllers/data/utils.ts +7 -7
  43. package/src/controllers/flow/__tests__/controller.test.ts +195 -0
  44. package/src/controllers/flow/__tests__/flow.test.ts +381 -0
  45. package/src/controllers/flow/controller.ts +13 -13
  46. package/src/controllers/flow/flow.ts +23 -23
  47. package/src/controllers/flow/index.ts +2 -2
  48. package/src/controllers/index.ts +5 -5
  49. package/src/controllers/validation/binding-tracker.ts +71 -59
  50. package/src/controllers/validation/controller.ts +104 -104
  51. package/src/controllers/validation/index.ts +2 -2
  52. package/src/controllers/view/asset-transform.ts +20 -20
  53. package/src/controllers/view/controller.ts +27 -27
  54. package/src/controllers/view/index.ts +4 -4
  55. package/src/controllers/view/store.ts +3 -3
  56. package/src/controllers/view/types.ts +7 -7
  57. package/src/data/__tests__/__snapshots__/dependency-tracker.test.ts.snap +64 -0
  58. package/src/data/__tests__/dependency-tracker.test.ts +146 -0
  59. package/src/data/__tests__/local-model.test.ts +46 -0
  60. package/src/data/__tests__/model.test.ts +78 -0
  61. package/src/data/dependency-tracker.ts +16 -16
  62. package/src/data/index.ts +4 -4
  63. package/src/data/local-model.ts +6 -6
  64. package/src/data/model.ts +17 -17
  65. package/src/data/noop-model.ts +1 -1
  66. package/src/expressions/__tests__/__snapshots__/parser.test.ts.snap +854 -0
  67. package/src/expressions/__tests__/evaluator-functions.test.ts +47 -0
  68. package/src/expressions/__tests__/evaluator.test.ts +410 -0
  69. package/src/expressions/__tests__/parser.test.ts +115 -0
  70. package/src/expressions/__tests__/utils.test.ts +44 -0
  71. package/src/expressions/evaluator-functions.ts +6 -6
  72. package/src/expressions/evaluator.ts +71 -67
  73. package/src/expressions/index.ts +4 -4
  74. package/src/expressions/parser.ts +102 -105
  75. package/src/expressions/types.ts +29 -21
  76. package/src/expressions/utils.ts +32 -21
  77. package/src/index.ts +13 -13
  78. package/src/logger/__tests__/consoleLogger.test.ts +46 -0
  79. package/src/logger/__tests__/noopLogger.test.ts +13 -0
  80. package/src/logger/__tests__/proxyLogger.test.ts +31 -0
  81. package/src/logger/__tests__/tapableLogger.test.ts +41 -0
  82. package/src/logger/consoleLogger.ts +9 -9
  83. package/src/logger/index.ts +5 -5
  84. package/src/logger/noopLogger.ts +1 -1
  85. package/src/logger/proxyLogger.ts +6 -6
  86. package/src/logger/tapableLogger.ts +7 -7
  87. package/src/logger/types.ts +2 -2
  88. package/src/player.ts +60 -58
  89. package/src/plugins/default-exp-plugin.ts +10 -10
  90. package/src/plugins/default-view-plugin.ts +29 -0
  91. package/src/plugins/flow-exp-plugin.ts +6 -6
  92. package/src/schema/__tests__/schema.test.ts +243 -0
  93. package/src/schema/index.ts +2 -2
  94. package/src/schema/schema.ts +24 -24
  95. package/src/schema/types.ts +4 -4
  96. package/src/string-resolver/__tests__/index.test.ts +361 -0
  97. package/src/string-resolver/index.ts +17 -17
  98. package/src/types.ts +17 -17
  99. package/src/utils/__tests__/replaceParams.test.ts +33 -0
  100. package/src/utils/index.ts +1 -1
  101. package/src/utils/replaceParams.ts +1 -1
  102. package/src/validator/__tests__/binding-map-splice.test.ts +53 -0
  103. package/src/validator/__tests__/validation-middleware.test.ts +127 -0
  104. package/src/validator/binding-map-splice.ts +5 -5
  105. package/src/validator/index.ts +4 -4
  106. package/src/validator/registry.ts +1 -1
  107. package/src/validator/types.ts +13 -13
  108. package/src/validator/validation-middleware.ts +15 -15
  109. package/src/view/__tests__/view.immutable.test.ts +269 -0
  110. package/src/view/__tests__/view.test.ts +959 -0
  111. package/src/view/builder/index.test.ts +69 -0
  112. package/src/view/builder/index.ts +3 -3
  113. package/src/view/index.ts +5 -5
  114. package/src/view/parser/__tests__/__snapshots__/parser.test.ts.snap +394 -0
  115. package/src/view/parser/__tests__/parser.test.ts +264 -0
  116. package/src/view/parser/index.ts +43 -33
  117. package/src/view/parser/types.ts +11 -11
  118. package/src/view/parser/utils.ts +5 -5
  119. package/src/view/plugins/__tests__/__snapshots__/template.test.ts.snap +278 -0
  120. package/src/view/plugins/__tests__/applicability.test.ts +265 -0
  121. package/src/view/plugins/__tests__/string.test.ts +122 -0
  122. package/src/view/plugins/__tests__/template.test.ts +724 -0
  123. package/src/view/plugins/applicability.ts +19 -19
  124. package/src/view/plugins/index.ts +4 -5
  125. package/src/view/plugins/options.ts +1 -1
  126. package/src/view/plugins/string-resolver.ts +22 -22
  127. package/src/view/plugins/switch.ts +22 -23
  128. package/src/view/plugins/template-plugin.ts +26 -27
  129. package/src/view/resolver/__tests__/dependencies.test.ts +321 -0
  130. package/src/view/resolver/__tests__/edgecases.test.ts +626 -0
  131. package/src/view/resolver/index.ts +42 -42
  132. package/src/view/resolver/types.ts +21 -20
  133. package/src/view/resolver/utils.ts +9 -9
  134. package/src/view/view.ts +32 -22
  135. package/types/binding/binding.d.ts +50 -0
  136. package/types/binding/index.d.ts +29 -0
  137. package/types/binding/resolver.d.ts +26 -0
  138. package/types/binding/utils.d.ts +12 -0
  139. package/types/binding-grammar/ast.d.ts +67 -0
  140. package/types/binding-grammar/custom/index.d.ts +4 -0
  141. package/types/binding-grammar/ebnf/index.d.ts +4 -0
  142. package/types/binding-grammar/ebnf/types.d.ts +75 -0
  143. package/types/binding-grammar/index.d.ts +5 -0
  144. package/types/binding-grammar/parsimmon/index.d.ts +4 -0
  145. package/types/controllers/constants/index.d.ts +45 -0
  146. package/types/controllers/constants/utils.d.ts +6 -0
  147. package/types/controllers/data/controller.d.ts +45 -0
  148. package/types/controllers/data/index.d.ts +2 -0
  149. package/types/controllers/data/utils.d.ts +14 -0
  150. package/types/controllers/flow/controller.d.ts +25 -0
  151. package/types/controllers/flow/flow.d.ts +50 -0
  152. package/types/controllers/flow/index.d.ts +3 -0
  153. package/types/controllers/index.d.ts +6 -0
  154. package/types/controllers/validation/binding-tracker.d.ts +32 -0
  155. package/types/controllers/validation/controller.d.ts +151 -0
  156. package/types/controllers/validation/index.d.ts +3 -0
  157. package/types/controllers/view/asset-transform.d.ts +19 -0
  158. package/types/controllers/view/controller.d.ts +37 -0
  159. package/types/controllers/view/index.d.ts +5 -0
  160. package/types/controllers/view/store.d.ts +20 -0
  161. package/types/controllers/view/types.d.ts +16 -0
  162. package/types/data/dependency-tracker.d.ts +49 -0
  163. package/types/data/index.d.ts +5 -0
  164. package/types/data/local-model.d.ts +16 -0
  165. package/types/data/model.d.ts +86 -0
  166. package/types/data/noop-model.d.ts +13 -0
  167. package/types/expressions/evaluator-functions.d.ts +15 -0
  168. package/types/expressions/evaluator.d.ts +52 -0
  169. package/types/expressions/index.d.ts +5 -0
  170. package/types/expressions/parser.d.ts +10 -0
  171. package/types/expressions/types.d.ts +144 -0
  172. package/types/expressions/utils.d.ts +12 -0
  173. package/types/index.d.ts +14 -0
  174. package/types/logger/consoleLogger.d.ts +17 -0
  175. package/types/logger/index.d.ts +6 -0
  176. package/types/logger/noopLogger.d.ts +10 -0
  177. package/types/logger/proxyLogger.d.ts +15 -0
  178. package/types/logger/tapableLogger.d.ts +23 -0
  179. package/types/logger/types.d.ts +6 -0
  180. package/types/player.d.ts +101 -0
  181. package/types/plugins/default-exp-plugin.d.ts +9 -0
  182. package/types/plugins/default-view-plugin.d.ts +9 -0
  183. package/types/plugins/flow-exp-plugin.d.ts +11 -0
  184. package/types/schema/index.d.ts +3 -0
  185. package/types/schema/schema.d.ts +36 -0
  186. package/types/schema/types.d.ts +38 -0
  187. package/types/string-resolver/index.d.ts +30 -0
  188. package/types/types.d.ts +73 -0
  189. package/types/utils/index.d.ts +2 -0
  190. package/types/utils/replaceParams.d.ts +9 -0
  191. package/types/validator/binding-map-splice.d.ts +10 -0
  192. package/types/validator/index.d.ts +5 -0
  193. package/types/validator/registry.d.ts +11 -0
  194. package/types/validator/types.d.ts +53 -0
  195. package/types/validator/validation-middleware.d.ts +36 -0
  196. package/types/view/builder/index.d.ts +35 -0
  197. package/types/view/index.d.ts +6 -0
  198. package/types/view/parser/index.d.ts +52 -0
  199. package/types/view/parser/types.d.ts +109 -0
  200. package/types/view/parser/utils.d.ts +6 -0
  201. package/types/view/plugins/applicability.d.ts +10 -0
  202. package/types/view/plugins/index.d.ts +5 -0
  203. package/types/view/plugins/options.d.ts +4 -0
  204. package/types/view/plugins/string-resolver.d.ts +13 -0
  205. package/types/view/plugins/switch.d.ts +14 -0
  206. package/types/view/plugins/template-plugin.d.ts +33 -0
  207. package/types/view/resolver/index.d.ts +73 -0
  208. package/types/view/resolver/types.d.ts +129 -0
  209. package/types/view/resolver/utils.d.ts +11 -0
  210. package/types/view/view.d.ts +37 -0
  211. package/dist/index.d.ts +0 -1814
  212. package/dist/player.dev.js +0 -11472
  213. package/dist/player.prod.js +0 -2
  214. package/src/view/plugins/plugin.ts +0 -21
@@ -1,7 +1,7 @@
1
- import { NestedError } from 'ts-nested-error';
2
- import type { SyncWaterfallHook } from 'tapable-ts';
3
- import type { PathNode, AnyNode } from '../binding-grammar';
4
- import { findInArray, maybeConvertToNum } from './utils';
1
+ import { NestedError } from "ts-nested-error";
2
+ import type { SyncWaterfallHook } from "tapable-ts";
3
+ import type { PathNode, AnyNode } from "../binding-grammar";
4
+ import { findInArray, maybeConvertToNum } from "./utils";
5
5
 
6
6
  export interface NormalizedResult {
7
7
  /** The normalized path */
@@ -33,7 +33,7 @@ export interface ResolveBindingASTHooks {
33
33
  export function resolveBindingAST(
34
34
  bindingPathNode: PathNode,
35
35
  options: ResolveBindingASTOptions,
36
- hooks?: ResolveBindingASTHooks
36
+ hooks?: ResolveBindingASTHooks,
37
37
  ): NormalizedResult {
38
38
  const context: Required<NormalizedResult> = {
39
39
  updates: {},
@@ -45,11 +45,11 @@ export function resolveBindingAST(
45
45
 
46
46
  /** Get the value for any child node */
47
47
  function getValueForNode(node: AnyNode): any {
48
- if (node.name === 'Value') {
48
+ if (node.name === "Value") {
49
49
  return node.value;
50
50
  }
51
51
 
52
- if (node.name === 'PathNode') {
52
+ if (node.name === "PathNode") {
53
53
  const nestedResolvedValue = resolveBindingAST(node, options);
54
54
 
55
55
  if (nestedResolvedValue.updates) {
@@ -61,17 +61,17 @@ export function resolveBindingAST(
61
61
 
62
62
  try {
63
63
  return options.convertToPath(
64
- options.getValue(nestedResolvedValue.path)
64
+ options.getValue(nestedResolvedValue.path),
65
65
  );
66
66
  } catch (e: any) {
67
67
  throw new NestedError(
68
68
  `Unable to resolve path segment: ${nestedResolvedValue.path}`,
69
- e
69
+ e,
70
70
  );
71
71
  }
72
72
  }
73
73
 
74
- if (node.name === 'Expression') {
74
+ if (node.name === "Expression") {
75
75
  try {
76
76
  const actualValue = options.evaluate(node.value);
77
77
 
@@ -86,8 +86,8 @@ export function resolveBindingAST(
86
86
 
87
87
  /** Handle when path segments are binding paths (foo.bar) or single segments (foo) */
88
88
  function appendPathSegments(segment: string | number) {
89
- if (typeof segment === 'string' && segment.indexOf('.') > -1) {
90
- segment.split('.').forEach((i) => {
89
+ if (typeof segment === "string" && segment.indexOf(".") > -1) {
90
+ segment.split(".").forEach((i) => {
91
91
  context.path.push(maybeConvertToNum(i));
92
92
  });
93
93
  } else {
@@ -101,16 +101,16 @@ export function resolveBindingAST(
101
101
  hooks?.beforeResolveNode.call(_node, { ...context, ...options }) ?? _node;
102
102
 
103
103
  switch (resolvedNode.name) {
104
- case 'Expression':
105
- case 'PathNode':
104
+ case "Expression":
105
+ case "PathNode":
106
106
  appendPathSegments(getValueForNode(resolvedNode));
107
107
  break;
108
108
 
109
- case 'Value':
109
+ case "Value":
110
110
  appendPathSegments(resolvedNode.value);
111
111
  break;
112
112
 
113
- case 'Query': {
113
+ case "Query": {
114
114
  // Look for an object at the path with the given key/val criteria
115
115
  const objToQuery: Record<string, any>[] =
116
116
  options.getValue(context.path) ?? [];
@@ -124,7 +124,7 @@ export function resolveBindingAST(
124
124
 
125
125
  if (index === undefined || index === -1) {
126
126
  context.updates[
127
- [...context.path, objToQuery.length, resolvedKey].join('.')
127
+ [...context.path, objToQuery.length, resolvedKey].join(".")
128
128
  ] = resolvedValue;
129
129
  context.path.push(objToQuery.length);
130
130
  } else {
@@ -134,8 +134,8 @@ export function resolveBindingAST(
134
134
  break;
135
135
  }
136
136
 
137
- case 'Concatenated':
138
- context.path.push(resolvedNode.value.map(getValueForNode).join(''));
137
+ case "Concatenated":
138
+ context.path.push(resolvedNode.value.map(getValueForNode).join(""));
139
139
  break;
140
140
 
141
141
  default:
@@ -1,8 +1,8 @@
1
- import type { BindingLike, BindingInstance } from './binding';
1
+ import type { BindingLike, BindingInstance } from "./binding";
2
2
 
3
3
  /** Check if the parameter representing a binding is already of the Binding class */
4
4
  export function isBinding(binding: BindingLike): binding is BindingInstance {
5
- return !(typeof binding === 'string' || Array.isArray(binding));
5
+ return !(typeof binding === "string" || Array.isArray(binding));
6
6
  }
7
7
 
8
8
  /** Convert the string to an int if you can, otherwise just return the original string */
@@ -20,14 +20,14 @@ export function maybeConvertToNum(i: string): string | number {
20
20
  * utility to convert binding into binding segments.
21
21
  */
22
22
  export function getBindingSegments(
23
- binding: BindingLike
23
+ binding: BindingLike,
24
24
  ): Array<string | number> {
25
25
  if (Array.isArray(binding)) {
26
26
  return binding;
27
27
  }
28
28
 
29
- if (typeof binding === 'string') {
30
- return binding.split('.');
29
+ if (typeof binding === "string") {
30
+ return binding.split(".");
31
31
  }
32
32
 
33
33
  return binding.asArray();
@@ -37,10 +37,10 @@ export function getBindingSegments(
37
37
  export function findInArray<T extends Record<string | number, object>>(
38
38
  array: Array<T>,
39
39
  key: string | number,
40
- value: T
40
+ value: T,
41
41
  ): number | undefined {
42
42
  return array.findIndex((obj) => {
43
- if (obj && typeof obj === 'object') {
43
+ if (obj && typeof obj === "object") {
44
44
  // Intentional double-equals because we want '4' to be coerced to 4
45
45
  // eslint-disable-next-line eqeqeq
46
46
  return obj[key] == value;
@@ -0,0 +1,64 @@
1
+ import { describe, test, expect } from "vitest";
2
+
3
+ import {
4
+ VALID_AST_PARSER_TESTS,
5
+ INVALID_AST_PARSER_TESTS,
6
+ VALID_AST_PARSER_CUSTOM_TESTS,
7
+ } from "./test-utils/ast-cases";
8
+ import type { ParserSuccessResult, ParserFailureResult } from "../ast";
9
+ import { parse as parseParsimmon } from "../parsimmon";
10
+ import { parse as parseEBNF } from "../ebnf";
11
+ import { parse as parseCustom } from "../custom";
12
+
13
+ describe("parsimmon", () => {
14
+ test.each(VALID_AST_PARSER_TESTS)("Parsimmon Valid: %s", (binding, AST) => {
15
+ const result = parseParsimmon(binding);
16
+
17
+ expect(result.status).toBe(true);
18
+ expect((result as ParserSuccessResult).path).toStrictEqual(AST);
19
+ });
20
+
21
+ test.each(INVALID_AST_PARSER_TESTS)("Parsimmon Invalid: %s", (binding) => {
22
+ const result = parseParsimmon(binding);
23
+ expect(result.status).toBe(false);
24
+ expect((result as ParserFailureResult).error.length > 0).toBe(true);
25
+ });
26
+ });
27
+
28
+ describe("ebnf", () => {
29
+ test.each(VALID_AST_PARSER_TESTS)("EBNF Valid: %s", (binding, AST) => {
30
+ const result = parseEBNF(binding);
31
+
32
+ expect(result.status).toBe(true);
33
+ expect((result as ParserSuccessResult).path).toStrictEqual(AST);
34
+ });
35
+
36
+ test.each(INVALID_AST_PARSER_TESTS)("EBNF Invalid: %s", (binding) => {
37
+ const result = parseEBNF(binding);
38
+ expect(result.status).toBe(false);
39
+ expect((result as ParserFailureResult).error.length > 0).toBe(true);
40
+ });
41
+ });
42
+
43
+ describe("custom", () => {
44
+ test.each(VALID_AST_PARSER_TESTS)("Custom Valid: %s", (binding, AST) => {
45
+ const result = parseCustom(binding);
46
+ expect(result.status).toBe(true);
47
+ expect((result as ParserSuccessResult).path).toStrictEqual(AST);
48
+ });
49
+
50
+ test.each(INVALID_AST_PARSER_TESTS)("Custom Invalid: %s", (binding) => {
51
+ const result = parseCustom(binding);
52
+ expect(result.status).toBe(false);
53
+ expect((result as ParserFailureResult).error.length > 0).toBe(true);
54
+ });
55
+
56
+ test.each(VALID_AST_PARSER_CUSTOM_TESTS)(
57
+ "Custom Unicode Valid: %s",
58
+ (binding, AST) => {
59
+ const result = parseCustom(binding);
60
+ expect(result.status).toBe(true);
61
+ expect((result as ParserSuccessResult).path).toStrictEqual(AST);
62
+ },
63
+ );
64
+ });
@@ -0,0 +1,198 @@
1
+ import type { PathNode } from "../../ast";
2
+ import {
3
+ toValue,
4
+ toQuery,
5
+ toConcatenatedNode,
6
+ toPath,
7
+ toExpression,
8
+ } from "../../ast";
9
+
10
+ export const VALID_AST_PARSER_TESTS: Array<[string, PathNode]> = [
11
+ // Basic ones
12
+ ["", toPath([])],
13
+ ["foo", toPath([toValue("foo")])],
14
+ ["foo.bar", toPath([toValue("foo"), toValue("bar")])],
15
+ ["1234567890_ABC_abc", toPath([toValue("1234567890_ABC_abc")])],
16
+ ["a-b-c-d-", toPath([toValue("a-b-c-d-")])],
17
+
18
+ // Queries
19
+ [
20
+ "foo[x=something]",
21
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
22
+ ],
23
+ [
24
+ "foo[x == something]",
25
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
26
+ ],
27
+ [
28
+ "foo[x === something]",
29
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
30
+ ],
31
+ [
32
+ `foo[x = 'something']`,
33
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
34
+ ],
35
+ [
36
+ 'foo[x == "something"]',
37
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
38
+ ],
39
+ [
40
+ 'foo["x" = "something"]',
41
+ toPath([toValue("foo"), toQuery(toValue("x"), toValue("something"))]),
42
+ ],
43
+ [
44
+ `foo['x'='hello [!@#$%^&*().]']`,
45
+ toPath([
46
+ toValue("foo"),
47
+ toQuery(toValue("x"), toValue("hello [!@#$%^&*().]")),
48
+ ]),
49
+ ],
50
+ [
51
+ `foo['Month'='New[.]]Mo,nth.']`,
52
+ toPath([
53
+ toValue("foo"),
54
+ toQuery(toValue("Month"), toValue("New[.]]Mo,nth.")),
55
+ ]),
56
+ ],
57
+
58
+ // Nested Paths
59
+ ["{{foo}}", toPath([toPath([toValue("foo")])])],
60
+ ["{{hello.world}}", toPath([toPath([toValue("hello"), toValue("world")])])],
61
+ [
62
+ "foo.bar.{{nested}}.baz",
63
+ toPath([
64
+ toValue("foo"),
65
+ toValue("bar"),
66
+ toPath([toValue("nested")]),
67
+ toValue("baz"),
68
+ ]),
69
+ ],
70
+ [
71
+ "foo[{{bar}}].baz",
72
+ toPath([toValue("foo"), toPath([toValue("bar")]), toValue("baz")]),
73
+ ],
74
+
75
+ // Brackets
76
+ [
77
+ "foo[a][b][c]",
78
+ toPath([toValue("foo"), toValue("a"), toValue("b"), toValue("c")]),
79
+ ],
80
+ [
81
+ `foo['a']["b"][c="bar"]`,
82
+ toPath([
83
+ toValue("foo"),
84
+ toValue("a"),
85
+ toValue("b"),
86
+ toQuery(toValue("c"), toValue("bar")),
87
+ ]),
88
+ ],
89
+ [
90
+ 'foo[a][b == " [x ]" ][c]',
91
+ toPath([
92
+ toValue("foo"),
93
+ toValue("a"),
94
+ toQuery(toValue("b"), toValue(" [x ]")),
95
+ toValue("c"),
96
+ ]),
97
+ ],
98
+
99
+ // Multi-Nodes
100
+ [
101
+ "_hello_{{world}}",
102
+ toPath([
103
+ toConcatenatedNode([toValue("_hello_"), toPath([toValue("world")])]),
104
+ ]),
105
+ ],
106
+ [
107
+ "_hello_{{world}}_foo_",
108
+ toPath([
109
+ toConcatenatedNode([
110
+ toValue("_hello_"),
111
+ toPath([toValue("world")]),
112
+ toValue("_foo_"),
113
+ ]),
114
+ ]),
115
+ ],
116
+ [
117
+ "{{hello}}{{world}}",
118
+ toPath([
119
+ toConcatenatedNode([
120
+ toPath([toValue("hello")]),
121
+ toPath([toValue("world")]),
122
+ ]),
123
+ ]),
124
+ ],
125
+ [
126
+ "{{hello}}_foobar_{{world}}",
127
+ toPath([
128
+ toConcatenatedNode([
129
+ toPath([toValue("hello")]),
130
+ toValue("_foobar_"),
131
+ toPath([toValue("world")]),
132
+ ]),
133
+ ]),
134
+ ],
135
+ [
136
+ "{{hello}}_world_",
137
+ toPath([
138
+ toConcatenatedNode([toPath([toValue("hello")]), toValue("_world_")]),
139
+ ]),
140
+ ],
141
+ [
142
+ "{{hello}}_world",
143
+ toPath([
144
+ toConcatenatedNode([toPath([toValue("hello")]), toValue("_world")]),
145
+ ]),
146
+ ],
147
+ [
148
+ "foo.{{hello}}_world",
149
+ toPath([
150
+ toValue("foo"),
151
+ toConcatenatedNode([toPath([toValue("hello")]), toValue("_world")]),
152
+ ]),
153
+ ],
154
+
155
+ // Expressions
156
+ [
157
+ "foo.`bar()`.baz",
158
+ toPath([toValue("foo"), toExpression("bar()"), toValue("baz")]),
159
+ ],
160
+ [
161
+ "foo`bar()`.baz",
162
+ toPath([
163
+ toConcatenatedNode([toValue("foo"), toExpression("bar()")]),
164
+ toValue("baz"),
165
+ ]),
166
+ ],
167
+ [
168
+ "foo[`bar()`].baz",
169
+ toPath([toValue("foo"), toExpression("bar()"), toValue("baz")]),
170
+ ],
171
+ [
172
+ 'foo["readonly" = `foo() == bar()`].baz',
173
+ toPath([
174
+ toValue("foo"),
175
+ toQuery(toValue("readonly"), toExpression("foo() == bar()")),
176
+ toValue("baz"),
177
+ ]),
178
+ ],
179
+ ];
180
+
181
+ export const INVALID_AST_PARSER_TESTS: Array<string> = [
182
+ " ",
183
+ "@#$%^&*()",
184
+ ".",
185
+ "foo.bar[",
186
+ "foo.bar.{{nested.}",
187
+ "foo.bar`not done()",
188
+ "{foo.bar",
189
+ ];
190
+
191
+ export const VALID_AST_PARSER_CUSTOM_TESTS: Array<[string, PathNode]> = [
192
+ ["foo‑<>~¡¢£", toPath([toValue("foo‑<>~¡¢£")])],
193
+ ["foo.bar<>~¡¢£", toPath([toValue("foo"), toValue("bar<>~¡¢£")])],
194
+ [
195
+ "foo[{{b‑ar}}].baz",
196
+ toPath([toValue("foo"), toPath([toValue("b‑ar")]), toValue("baz")]),
197
+ ],
198
+ ];
@@ -0,0 +1,66 @@
1
+ import { VALID_AST_PARSER_TESTS } from "./ast-cases";
2
+ import { parse as parseParsimmon } from "../../parsimmon";
3
+ import { parse as parseEBNF } from "../../ebnf";
4
+ import { parse as parseCustom } from "../../custom";
5
+
6
+ import type { Parser } from "../../ast";
7
+
8
+ const parsers: Array<{
9
+ /** The name of the parser being tested */
10
+ name: string;
11
+
12
+ /** The parse function */
13
+ parser: Parser;
14
+ }> = [
15
+ { name: "parsimmon", parser: parseParsimmon },
16
+ { name: "ebnf", parser: parseEBNF },
17
+ { name: "custom", parser: parseCustom },
18
+ ];
19
+
20
+ const VALID_ITERATIONS = 1000;
21
+
22
+ /** Execute 1 iteration of the parser test */
23
+ const runOnce = (parser: Parser): number => {
24
+ const start = Date.now();
25
+
26
+ for (const testCase of VALID_AST_PARSER_TESTS) {
27
+ parser(testCase[0]);
28
+ }
29
+
30
+ const end = Date.now();
31
+
32
+ return end - start;
33
+ };
34
+
35
+ /** Run all parser perf tests */
36
+ export const testAll = () => {
37
+ const results: Array<{
38
+ /** The name of the parser */
39
+ name: string;
40
+
41
+ /** How long it took overall */
42
+ time: number;
43
+
44
+ /** Average number of bindings parsed per sec */
45
+ opsPerSec: number;
46
+ }> = [];
47
+
48
+ for (const parser of parsers) {
49
+ const runs = [];
50
+
51
+ for (let i = 0; i < VALID_ITERATIONS; i++) {
52
+ runs.push(runOnce(parser.parser));
53
+ }
54
+
55
+ const total = runs.reduce((s, n) => s + n);
56
+ const opsPerSec =
57
+ (VALID_ITERATIONS * VALID_AST_PARSER_TESTS.length) / (total / 1000);
58
+ results.push({
59
+ name: parser.name,
60
+ time: total,
61
+ opsPerSec,
62
+ });
63
+ }
64
+
65
+ console.table(results);
66
+ };
@@ -7,7 +7,7 @@ export interface Node<T extends string> {
7
7
  * An AST node that represents a nested path in the model
8
8
  * foo.{{bar}}.baz (this is {{bar}})
9
9
  */
10
- export interface PathNode extends Node<'PathNode'> {
10
+ export interface PathNode extends Node<"PathNode"> {
11
11
  /** The path in the model that this node represents */
12
12
  path: Array<AnyNode>;
13
13
  }
@@ -16,7 +16,7 @@ export interface PathNode extends Node<'PathNode'> {
16
16
  * A segment representing a query
17
17
  * [foo=bar]
18
18
  */
19
- export interface QueryNode extends Node<'Query'> {
19
+ export interface QueryNode extends Node<"Query"> {
20
20
  /** The key to query */
21
21
  key: AnyNode;
22
22
 
@@ -25,52 +25,52 @@ export interface QueryNode extends Node<'Query'> {
25
25
  }
26
26
 
27
27
  /** A simple segment */
28
- export interface ValueNode extends Node<'Value'> {
28
+ export interface ValueNode extends Node<"Value"> {
29
29
  /** The segment value */
30
30
  value: string | number;
31
31
  }
32
32
 
33
33
  /** A nested expression */
34
- export interface ExpressionNode extends Node<'Expression'> {
34
+ export interface ExpressionNode extends Node<"Expression"> {
35
35
  /** The expression */
36
36
  value: string;
37
37
  }
38
38
 
39
39
  /** Helper to create a value node */
40
40
  export const toValue = (value: string | number): ValueNode => ({
41
- name: 'Value',
41
+ name: "Value",
42
42
  value,
43
43
  });
44
44
 
45
45
  /** Helper to create an expression node */
46
46
  export const toExpression = (value: string): ExpressionNode => ({
47
- name: 'Expression',
47
+ name: "Expression",
48
48
  value,
49
49
  });
50
50
 
51
51
  /** Helper to create a nested path node */
52
52
  export const toPath = (path: Array<AnyNode>): PathNode => ({
53
- name: 'PathNode',
53
+ name: "PathNode",
54
54
  path,
55
55
  });
56
56
 
57
57
  /** Helper to create a query node */
58
58
  export const toQuery = (key: AnyNode, value?: AnyNode): QueryNode => ({
59
- name: 'Query',
59
+ name: "Query",
60
60
  key,
61
61
  value,
62
62
  });
63
63
 
64
64
  /** Create a concat node */
65
65
  export const toConcatenatedNode = (
66
- values: Array<PathNode | ValueNode | ExpressionNode>
66
+ values: Array<PathNode | ValueNode | ExpressionNode>,
67
67
  ): PathNode | ValueNode | ConcatenatedNode | ExpressionNode => {
68
68
  if (values.length === 1) {
69
69
  return values[0];
70
70
  }
71
71
 
72
72
  return {
73
- name: 'Concatenated',
73
+ name: "Concatenated",
74
74
  value: values,
75
75
  };
76
76
  };
@@ -79,7 +79,7 @@ export const toConcatenatedNode = (
79
79
  * A binding segment that's multiple smaller ones
80
80
  * {{foo}}_bar_{{baz}}
81
81
  */
82
- export interface ConcatenatedNode extends Node<'Concatenated'> {
82
+ export interface ConcatenatedNode extends Node<"Concatenated"> {
83
83
  /** A list of nested paths, or value nodes to concat together to form a segment */
84
84
  value: Array<PathNode | ValueNode | ExpressionNode>;
85
85
  }
@@ -6,24 +6,24 @@ import type {
6
6
  ValueNode,
7
7
  QueryNode,
8
8
  ExpressionNode,
9
- } from '../ast';
9
+ } from "../ast";
10
10
  import {
11
11
  toValue,
12
12
  toPath,
13
13
  toConcatenatedNode,
14
14
  toQuery,
15
15
  toExpression,
16
- } from '../ast';
17
-
18
- const SEGMENT_SEPARATOR = '.';
19
- const OPEN_CURL = '{';
20
- const CLOSE_CURL = '}';
21
- const OPEN_BRACKET = '[';
22
- const CLOSE_BRACKET = ']';
23
- const EQUALS = '=';
16
+ } from "../ast";
17
+
18
+ const SEGMENT_SEPARATOR = ".";
19
+ const OPEN_CURL = "{";
20
+ const CLOSE_CURL = "}";
21
+ const OPEN_BRACKET = "[";
22
+ const CLOSE_BRACKET = "]";
23
+ const EQUALS = "=";
24
24
  const SINGLE_QUOTE = "'";
25
25
  const DOUBLE_QUOTE = '"';
26
- const BACK_TICK = '`';
26
+ const BACK_TICK = "`";
27
27
  // const IDENTIFIER_REGEX = /[\w\-@]+/;
28
28
 
29
29
  /** A _faster_ way to match chars instead of a regex. */
@@ -65,14 +65,13 @@ export const parse: Parser = (path) => {
65
65
 
66
66
  ch = path.charAt(index);
67
67
  index += 1;
68
- // console.log(`Index: ${index} Char: ${ch}`);
69
68
  return ch;
70
69
  };
71
70
 
72
71
  /** gobble all whitespace */
73
72
  const whitespace = () => {
74
73
  /* eslint-disable no-unmodified-loop-condition */
75
- while (ch === ' ') {
74
+ while (ch === " ") {
76
75
  next();
77
76
  }
78
77
  };
@@ -146,15 +145,13 @@ export const parse: Parser = (path) => {
146
145
  const nestedPath = (): PathNode | undefined => {
147
146
  if (ch === OPEN_CURL) {
148
147
  next(OPEN_CURL);
149
- if (ch === OPEN_CURL) {
150
- next(OPEN_CURL);
151
-
152
- /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
153
- const modelRef = parsePath();
154
- next(CLOSE_CURL);
155
- next(CLOSE_CURL);
156
- return modelRef;
157
- }
148
+ next(OPEN_CURL);
149
+
150
+ /* eslint-disable-next-line @typescript-eslint/no-use-before-define */
151
+ const modelRef = parsePath();
152
+ next(CLOSE_CURL);
153
+ next(CLOSE_CURL);
154
+ return modelRef;
158
155
  }
159
156
  };
160
157
 
@@ -254,7 +251,7 @@ export const parse: Parser = (path) => {
254
251
  const parseSegmentAndBrackets = (): Array<AnyNode> => {
255
252
  // try to parse a segment first
256
253
 
257
- const parsed = [];
254
+ const parsed: Array<AnyNode> = [];
258
255
 
259
256
  const firstSegment = segment();
260
257