clarity-pattern-parser 6.0.0 → 7.0.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 (94) hide show
  1. package/TODO.md +1 -76
  2. package/dist/ast/Node.d.ts +1 -0
  3. package/dist/grammar/Grammar.d.ts +17 -0
  4. package/dist/grammar/patterns/andLiteral.d.ts +2 -0
  5. package/dist/grammar/patterns/comment.d.ts +2 -0
  6. package/dist/grammar/patterns/grammar.d.ts +2 -0
  7. package/dist/grammar/patterns/literal.d.ts +2 -0
  8. package/dist/grammar/patterns/name.d.ts +2 -0
  9. package/dist/grammar/patterns/orLiteral.d.ts +2 -0
  10. package/dist/grammar/patterns/pattern.d.ts +2 -0
  11. package/dist/grammar/patterns/regexLiteral.d.ts +2 -0
  12. package/dist/grammar/patterns/repeatLiteral.d.ts +3 -0
  13. package/dist/grammar/patterns/spaces.d.ts +2 -0
  14. package/dist/grammar/patterns/statement.d.ts +2 -0
  15. package/dist/index.browser.js +1205 -550
  16. package/dist/index.browser.js.map +1 -1
  17. package/dist/index.d.ts +5 -4
  18. package/dist/index.esm.js +1203 -549
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1203 -548
  21. package/dist/index.js.map +1 -1
  22. package/dist/intellisense/AutoComplete.d.ts +9 -7
  23. package/dist/intellisense/Suggestion.d.ts +1 -2
  24. package/dist/patterns/And.d.ts +2 -1
  25. package/dist/patterns/Cursor.d.ts +1 -0
  26. package/dist/patterns/CursorHistory.d.ts +2 -1
  27. package/dist/patterns/FiniteRepeat.d.ts +39 -0
  28. package/dist/patterns/InfiniteRepeat.d.ts +47 -0
  29. package/dist/patterns/Literal.d.ts +2 -1
  30. package/dist/patterns/Not.d.ts +2 -1
  31. package/dist/patterns/Or.d.ts +2 -1
  32. package/dist/patterns/Pattern.d.ts +3 -2
  33. package/dist/patterns/Reference.d.ts +2 -1
  34. package/dist/patterns/Regex.d.ts +2 -1
  35. package/dist/patterns/Repeat.d.ts +19 -22
  36. package/jest.config.js +0 -1
  37. package/jest.coverage.config.js +13 -0
  38. package/package.json +3 -3
  39. package/src/ast/Node.test.ts +21 -0
  40. package/src/ast/Node.ts +12 -6
  41. package/src/grammar/Grammar.test.ts +288 -0
  42. package/src/grammar/Grammar.ts +234 -0
  43. package/src/grammar/patterns/andLiteral.ts +8 -0
  44. package/src/grammar/patterns/comment.ts +3 -0
  45. package/src/grammar/patterns/grammar.ts +19 -0
  46. package/src/grammar/patterns/literal.ts +5 -0
  47. package/src/grammar/patterns/name.ts +3 -0
  48. package/src/grammar/patterns/orLiteral.ts +8 -0
  49. package/src/grammar/patterns/pattern.ts +13 -0
  50. package/src/grammar/patterns/regexLiteral.ts +4 -0
  51. package/src/grammar/patterns/repeatLiteral.ts +72 -0
  52. package/src/grammar/patterns/spaces.ts +4 -0
  53. package/src/grammar/patterns/statement.ts +35 -0
  54. package/src/grammar/spec.md +142 -0
  55. package/src/index.ts +6 -3
  56. package/src/intellisense/AutoComplete.test.ts +125 -39
  57. package/src/intellisense/AutoComplete.ts +52 -36
  58. package/src/intellisense/Suggestion.ts +1 -2
  59. package/src/intellisense/css/cssValue.ts +1 -1
  60. package/src/intellisense/css/method.ts +1 -1
  61. package/src/intellisense/css/values.ts +1 -1
  62. package/src/intellisense/javascript/Javascript.test.ts +34 -11
  63. package/src/intellisense/javascript/arrayLiteral.ts +1 -1
  64. package/src/intellisense/javascript/{expressionStatement.ts → assignment.ts} +7 -8
  65. package/src/intellisense/javascript/expression.ts +45 -27
  66. package/src/intellisense/javascript/infixOperator.ts +6 -2
  67. package/src/intellisense/javascript/invocation.ts +1 -1
  68. package/src/intellisense/javascript/keywords.ts +3 -0
  69. package/src/intellisense/javascript/objectAccess.ts +9 -0
  70. package/src/intellisense/javascript/objectLiteral.ts +3 -3
  71. package/src/intellisense/javascript/parameters.ts +1 -1
  72. package/src/intellisense/javascript/propertyAccess.ts +8 -3
  73. package/src/intellisense/javascript/stringLiteral.ts +14 -8
  74. package/src/patterns/And.test.ts +16 -3
  75. package/src/patterns/And.ts +25 -17
  76. package/src/patterns/Cursor.ts +4 -0
  77. package/src/patterns/CursorHistory.ts +34 -5
  78. package/src/patterns/FiniteRepeat.test.ts +481 -0
  79. package/src/patterns/FiniteRepeat.ts +231 -0
  80. package/src/patterns/InfiniteRepeat.test.ts +296 -0
  81. package/src/patterns/InfiniteRepeat.ts +329 -0
  82. package/src/patterns/Literal.test.ts +13 -4
  83. package/src/patterns/Literal.ts +5 -1
  84. package/src/patterns/Not.test.ts +20 -9
  85. package/src/patterns/Not.ts +5 -1
  86. package/src/patterns/Or.test.ts +18 -7
  87. package/src/patterns/Or.ts +11 -1
  88. package/src/patterns/Pattern.ts +3 -2
  89. package/src/patterns/Reference.test.ts +18 -8
  90. package/src/patterns/Reference.ts +5 -1
  91. package/src/patterns/Regex.test.ts +13 -4
  92. package/src/patterns/Regex.ts +5 -1
  93. package/src/patterns/Repeat.test.ts +162 -158
  94. package/src/patterns/Repeat.ts +95 -226
@@ -0,0 +1,72 @@
1
+ import { And } from "../../patterns/And";
2
+ import { Literal } from "../../patterns/Literal";
3
+ import { Or } from "../../patterns/Or";
4
+ import { Regex } from "../../patterns/Regex";
5
+ import { name } from "./name";
6
+ import { spaces } from "./spaces";
7
+
8
+ const optionalIsOptional = new Literal("is-optional", "?", true);
9
+ const patternName = name.clone("pattern-name");
10
+
11
+ export const pattern = new And("pattern", [
12
+ patternName,
13
+ optionalIsOptional,
14
+ ]);
15
+
16
+ const optionalSpaces = spaces.clone("optional-spaces", true);
17
+ const dividerPattern = name.clone("divider-pattern");
18
+
19
+ const openBracket = new Literal("open-bracket", "{");
20
+ const closeBracket = new Literal("close-bracket", "}");
21
+ const comma = new Literal("comma", ",");
22
+ const integer = new Regex("integer", "([1-9][0-9]*)|0");
23
+ integer.setTokens(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]);
24
+
25
+ const optionalInteger = integer.clone("integer", true)
26
+
27
+ const bounds = new And("bounds", [
28
+ openBracket,
29
+ optionalSpaces,
30
+ optionalInteger.clone("min"),
31
+ optionalSpaces,
32
+ comma,
33
+ optionalSpaces,
34
+ optionalInteger.clone("max"),
35
+ optionalSpaces,
36
+ closeBracket
37
+ ]);
38
+
39
+ const exactCount = new And("exact-count", [
40
+ openBracket,
41
+ optionalSpaces,
42
+ integer,
43
+ optionalSpaces,
44
+ closeBracket,
45
+ ]);
46
+
47
+ const quantifierShorthand = new Regex("quantifier-shorthand", "\\*|\\+");
48
+ quantifierShorthand.setTokens(["*", "+"]);
49
+ const quantifier = new Or("quantifier", [
50
+ quantifierShorthand,
51
+ exactCount,
52
+ bounds
53
+ ]);
54
+
55
+ const optional = new Literal("is-optional", "?", true);
56
+ const trimDivider = new Literal("trim-divider", "-t");
57
+ const openParen = new Literal("open-paren", "(");
58
+ const closeParen = new Literal("close-paren", ")");
59
+ const dividerComma = new Regex("divider-comma", "\\s*,\\s*");
60
+ dividerComma.setTokens([", "]);
61
+
62
+ export const repeatLiteral = new And("repeat-literal", [
63
+ openParen,
64
+ optionalSpaces,
65
+ pattern,
66
+ optional,
67
+ new And("optional-divider-section", [dividerComma, dividerPattern], true),
68
+ optionalSpaces,
69
+ closeParen,
70
+ new And("quantifier-section", [optionalSpaces, quantifier]),
71
+ new And("optional-trim-divider-section", [spaces, trimDivider], true)
72
+ ]);
@@ -0,0 +1,4 @@
1
+ import { Regex } from "../../patterns/Regex";
2
+
3
+ export const spaces = new Regex("spaces", "[ \\t]+");
4
+ spaces.setTokens([" "]);
@@ -0,0 +1,35 @@
1
+ import { And } from "../../patterns/And";
2
+ import { Literal } from "../../patterns/Literal";
3
+ import { Or } from "../../patterns/Or";
4
+ import { andLiteral } from "./andLiteral";
5
+ import { name } from "./name";
6
+ import { orLiteral } from "./orLiteral";
7
+ import { regexLiteral } from "./regexLiteral";
8
+ import { repeatLiteral } from "./repeatLiteral";
9
+ import { spaces } from "./spaces";
10
+ import { literal } from "./literal";
11
+ import { comment } from "./comment";
12
+
13
+ const optionalSpaces = spaces.clone("optional-spaces", true);
14
+ const assignOperator = new Literal("assign-operator", "=");
15
+ const optionalComment = comment.clone("inline-comment", true);
16
+
17
+ const statements = new Or("statements", [
18
+ literal,
19
+ regexLiteral,
20
+ orLiteral,
21
+ andLiteral,
22
+ repeatLiteral,
23
+ ]);
24
+
25
+ export const statement = new And("statement", [
26
+ optionalSpaces,
27
+ name,
28
+ optionalSpaces,
29
+ assignOperator,
30
+ optionalSpaces,
31
+ statements,
32
+ optionalSpaces,
33
+ optionalComment,
34
+ optionalSpaces,
35
+ ]);
@@ -0,0 +1,142 @@
1
+ # Grammar
2
+
3
+ ## Literal
4
+
5
+ ```
6
+ name = "Literal"
7
+ ```
8
+
9
+ ## Regex
10
+
11
+ ```
12
+ digits = /\d+/
13
+ ```
14
+
15
+ ### Regex Caveats
16
+
17
+ Do not use "^" at the beginning or "$" at the end of your regular expression. If
18
+ you are creating a regular expression that is concerned about the beginning and
19
+ end of the text you should probably just use a regular expression.
20
+
21
+ ## Or
22
+ This succeeds if it matches any of the options.
23
+ ```
24
+ multiply = "*"
25
+ divide = "/"
26
+ operators = mulitply | divide
27
+ ```
28
+
29
+ ## And
30
+ This succeeds if it matches all of the patterns in the order they are placed.
31
+ ```
32
+ space = " "
33
+ first-name = "John"
34
+ last-name = "Doe"
35
+ full-name = first-name & space & last-name
36
+ ```
37
+
38
+ ### Optional pattern
39
+ Patterns within the and sequence can be optional.
40
+ ```
41
+ space = " "
42
+ first-name = /\w+/
43
+ last-name = /\w+/
44
+ middle-name = /\w+/
45
+ middle-name-with-space = middle-name & space
46
+
47
+ full-name = first-name & space & middle-name-with-space? & last-name
48
+ ```
49
+
50
+ ### Negative Look Ahead
51
+ This will ensure that the first name isn't `Jack` before continuing to match for
52
+ a name.
53
+
54
+ ```
55
+ space = " "
56
+ first-name = /\w+/
57
+ last-name = /\w+/
58
+ middle-name = /\w+/
59
+ middle-name-with-space = m-middle-name & space
60
+ jack = "Jack"
61
+
62
+ full-name = !jack & first-name & space & middle-name-with-space? & last-name
63
+ ```
64
+
65
+ ## Repeat
66
+ ```
67
+ digit = /\d/
68
+ digits = (digit)*
69
+ ```
70
+
71
+ ### Zero Or More Pattern
72
+
73
+ ```
74
+ digit = /\d/
75
+ comma = ","
76
+ digits = (digit, comma)*
77
+ ```
78
+
79
+ ### Zero Or More & Trim Trailing Divider
80
+ ```
81
+ digit = /\d/
82
+ comma = ","
83
+ digits = (digit, comma)* -t
84
+ ```
85
+
86
+ This is a useful feature if you don't want the divider to be the last pattern found. Let's look at the example below to understand.
87
+
88
+ ```ts
89
+ const expression = `
90
+ digit = /\d/
91
+ comma = ","
92
+ digits = (digit, comma)* -t
93
+ `;
94
+
95
+ const { digits } = Gammar.parse(expression);
96
+
97
+ let result = digits.exec("1,2,3");
98
+ expect(result.ast?.value).toBe("1,2,3");
99
+
100
+ result = digits.exec("1,2,");
101
+ expect(result.ast).toBeNull();
102
+ ```
103
+
104
+ ### Zero Or More With Optional Repeated Pattern
105
+
106
+ ```
107
+ digit = /\d/
108
+ comma = ","
109
+ digits = (digit?, comma)*
110
+ ```
111
+
112
+ ### One Or More With Optional Repeated Pattern
113
+
114
+ ```
115
+ digit = /\d/
116
+ comma = ","
117
+ digits = (digit?, comma)+
118
+ ```
119
+
120
+ ### Upper Limit
121
+
122
+ ```
123
+ digit = /\d/
124
+ comma = ","
125
+ digits = (digit, comma){,3}
126
+ ```
127
+
128
+ ### Bounded
129
+
130
+ ```
131
+ digit = /\d/
132
+ comma = ","
133
+ digits = (digit, comma){1,3}
134
+ ```
135
+
136
+ ### Lower Limit
137
+
138
+ ```
139
+ digit = /\d/
140
+ comma = ","
141
+ digits = (digit, comma){1,3}
142
+ ```
package/src/index.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { Node } from "./ast/Node";
2
+ import { Grammar } from "./grammar/Grammar";
3
+ import { Suggestion } from "./intellisense/Suggestion";
4
+ import { SuggestionOption } from "./intellisense/SuggestionOption";
5
+ import { AutoComplete, AutoCompleteOptions } from './intellisense/AutoComplete';
2
6
  import { Cursor } from "./patterns/Cursor";
3
7
  import { Regex } from "./patterns/Regex";
4
8
  import { And } from "./patterns/And";
@@ -9,15 +13,14 @@ import { Repeat } from "./patterns/Repeat";
9
13
  import { ParseError } from "./patterns/ParseError";
10
14
  import { Pattern } from "./patterns/Pattern";
11
15
  import { Reference } from "./patterns/Reference";
12
- import { AutoComplete } from './intellisense/AutoComplete';
13
16
  import { CursorHistory, Match } from "./patterns/CursorHistory";
14
17
  import { ParseResult } from "./patterns/ParseResult";
15
- import { Suggestion } from "./intellisense/Suggestion";
16
- import { SuggestionOption } from "./intellisense/SuggestionOption";
17
18
 
18
19
  export {
19
20
  Node,
21
+ Grammar,
20
22
  AutoComplete,
23
+ AutoCompleteOptions,
21
24
  Suggestion,
22
25
  SuggestionOption,
23
26
  And,
@@ -1,5 +1,4 @@
1
1
  import { And } from "../patterns/And";
2
- import { findPattern } from "../patterns/findPattern";
3
2
  import { Literal } from "../patterns/Literal";
4
3
  import { Or } from "../patterns/Or";
5
4
  import { Regex } from "../patterns/Regex";
@@ -10,11 +9,11 @@ describe("AutoComplete", () => {
10
9
  test("No Text", () => {
11
10
  const name = new Literal("name", "Name");
12
11
  const autoComplete = new AutoComplete(name);
13
- let result = autoComplete.suggest("");
12
+ let result = autoComplete.suggestFor("");
14
13
 
15
14
  expect(result.options[0].text).toBe("Name");
16
15
  expect(result.options[0].startIndex).toBe(0);
17
- expect(result.nextPatterns[0]).toBe(name);
16
+ expect(result.errorAtIndex).toBe(0);
18
17
  expect(result.isComplete).toBeFalsy();
19
18
  });
20
19
 
@@ -26,11 +25,11 @@ describe("AutoComplete", () => {
26
25
  const name = new And("name", [john, space, new Or("last-name", [smith, doe])]);
27
26
 
28
27
  const autoComplete = new AutoComplete(name);
29
- const result = autoComplete.suggest("John Doe");
28
+ const result = autoComplete.suggestFor("John Doe");
30
29
 
31
30
  expect(result.ast?.value).toBe("John Doe");
32
31
  expect(result.options.length).toBe(0);
33
- expect(result.nextPatterns.length).toBe(0);
32
+ expect(result.errorAtIndex).toBeNull();
34
33
  expect(result.isComplete).toBeTruthy();
35
34
  expect(result.cursor).not.toBeNull();
36
35
  });
@@ -42,14 +41,20 @@ describe("AutoComplete", () => {
42
41
  const smith = new Literal("smith", "Smith");
43
42
  const name = new And("name", [john, space, new Or("last-name", [smith, doe])]);
44
43
 
44
+ const text = "John "
45
45
  const autoComplete = new AutoComplete(name);
46
- const result = autoComplete.suggest("John ");
46
+ const result = autoComplete.suggestFor(text);
47
+ const expectedOptions = [{
48
+ text: "Doe",
49
+ startIndex: 5
50
+ }, {
51
+ text: "Smith",
52
+ startIndex: 5
53
+ }];
47
54
 
48
55
  expect(result.ast).toBeNull();
49
- expect(result.options.length).toBe(2);
50
- expect(result.nextPatterns.length).toBe(1);
51
- expect(result.nextPatterns[0].type).toBe("or");
52
- expect(result.nextPatterns[0].name).toBe("last-name");
56
+ expect(result.options).toEqual(expectedOptions);
57
+ expect(result.errorAtIndex).toBe(text.length)
53
58
  expect(result.isComplete).toBeFalsy();
54
59
  expect(result.cursor).not.toBeNull();
55
60
  });
@@ -64,12 +69,17 @@ describe("AutoComplete", () => {
64
69
 
65
70
  divider.setTokens([", "])
66
71
 
67
- const autoComplete = new AutoComplete(new Repeat("last-names", name, divider));
68
- const result = autoComplete.suggest("John Doe");
69
-
70
- expect(result.ast?.value).toBe("John Doe");
71
- expect(result.options.length).toBe(1);
72
- expect(result.nextPatterns.length).toBe(result.options.length);
72
+ const text = "John Doe";
73
+ const autoComplete = new AutoComplete(new Repeat("last-names", name, { divider }));
74
+ const result = autoComplete.suggestFor(text);
75
+ const expectedOptions = [{
76
+ text: ", ",
77
+ startIndex: 8
78
+ }];
79
+
80
+ expect(result.ast?.value).toBe(text);
81
+ expect(result.options).toEqual(expectedOptions);
82
+ expect(result.errorAtIndex).toBeNull()
73
83
  expect(result.isComplete).toBeTruthy();
74
84
  expect(result.cursor).not.toBeNull();
75
85
  });
@@ -77,36 +87,51 @@ describe("AutoComplete", () => {
77
87
  test("Partial", () => {
78
88
  const name = new Literal("name", "Name");
79
89
  const autoComplete = new AutoComplete(name);
80
- let result = autoComplete.suggest("Na");
90
+ // Use deprecated suggest for code coverage.
91
+ const result = autoComplete.suggestFor("Na");
92
+ const expectedOptions = [{
93
+ text: "me",
94
+ startIndex: 2
95
+ }];
81
96
 
82
- expect(result.options[0].text).toBe("me");
83
- expect(result.options[0].startIndex).toBe(2);
84
- expect(result.nextPatterns[0]).toBe(name);
97
+ expect(result.ast).toBeNull();
98
+ expect(result.options).toEqual(expectedOptions);
99
+ expect(result.errorAtIndex).toBe(2);
85
100
  expect(result.isComplete).toBeFalsy();
101
+ expect(result.cursor).not.toBeNull();
86
102
  });
87
103
 
88
104
  test("Partial Match With Bad Characters", () => {
89
105
  const name = new Literal("name", "Name");
90
106
  const autoComplete = new AutoComplete(name);
91
- let result = autoComplete.suggest("Ni");
107
+ const result = autoComplete.suggestFor("Ni");
108
+
109
+ const expectedOptions = [{
110
+ text: "ame",
111
+ startIndex: 1
112
+ }];
92
113
 
93
- expect(result.options[0].text).toBe("ame");
94
- expect(result.options[0].startIndex).toBe(1);
95
- //expect(result.nextPattern).toBe(name);
114
+ expect(result.ast).toBeNull();
115
+ expect(result.options).toEqual(expectedOptions);
116
+ expect(result.errorAtIndex).toBe(1);
96
117
  expect(result.isComplete).toBeFalsy();
118
+ expect(result.cursor).not.toBeNull();
97
119
  });
98
120
 
99
121
  test("Complete", () => {
100
122
  const name = new Literal("name", "Name");
101
123
  const autoComplete = new AutoComplete(name);
102
- let result = autoComplete.suggest("Name");
124
+ const text = "Name"
125
+ const result = autoComplete.suggestFor(text);
103
126
 
104
- expect(result.options.length).toBe(0);
105
- expect(result.nextPatterns.length).toBe(0);
127
+ expect(result.ast?.value).toBe(text);
128
+ expect(result.options).toEqual([]);
129
+ expect(result.errorAtIndex).toBeNull();
106
130
  expect(result.isComplete).toBeTruthy();
131
+ expect(result.cursor).not.toBeNull();
107
132
  });
108
133
 
109
- test("Options", ()=>{
134
+ test("Options AutoComplete on Composing Pattern", () => {
110
135
  const autoCompleteOptions: AutoCompleteOptions = {
111
136
  greedyPatternNames: ["space"],
112
137
  customTokens: {
@@ -123,28 +148,89 @@ describe("AutoComplete", () => {
123
148
  const lastName = new Or("last-name", [doe, smith]);
124
149
  const fullName = new And("full-name", [firstName, space, lastName]);
125
150
 
126
- const text = "John";
151
+ const text = "Jack";
127
152
  const autoComplete = new AutoComplete(fullName, autoCompleteOptions);
128
- const { options, ast, nextPatterns } = autoComplete.suggest(text);
153
+ const { options, ast, errorAtIndex } = autoComplete.suggestFor(text);
154
+
129
155
  const expectedOptions = [
130
- {text: " Doe", startIndex: 4},
131
- {text: " Smith", startIndex: 4},
132
- {text: " Sparrow", startIndex: 4},
156
+ { text: " Doe", startIndex: 4 },
157
+ { text: " Smith", startIndex: 4 },
158
+ { text: " Sparrow", startIndex: 4 },
133
159
  ];
134
160
 
135
- const results = expectedOptions.map(o=>text.slice(0, o.startIndex) + o.text);
161
+ const results = expectedOptions.map(o => text.slice(0, o.startIndex) + o.text);
136
162
  const expectedResults = [
137
- "John Doe",
138
- "John Smith",
139
- "John Sparrow",
163
+ "Jack Doe",
164
+ "Jack Smith",
165
+ "Jack Sparrow",
140
166
  ]
141
167
 
168
+ expect(ast).toBeNull();
169
+ expect(errorAtIndex).toBe(4);
142
170
  expect(options).toEqual(expectedOptions);
171
+ expect(results).toEqual(expectedResults);
172
+
173
+ });
174
+
175
+ test("Options AutoComplete On Leaf Pattern", () => {
176
+ const autoCompleteOptions: AutoCompleteOptions = {
177
+ greedyPatternNames: ["space"],
178
+ customTokens: {
179
+ "space": [" "]
180
+ }
181
+ };
182
+
183
+ const jack = new Literal("jack", "Jack");
184
+ const john = new Literal("john", "John");
185
+ const space = new Literal("space", " ");
186
+ const doe = new Literal("doe", "Doe");
187
+ const smith = new Literal("smith", "Smith");
188
+ const firstName = new Or("first-name", [jack, john]);
189
+ const lastName = new Or("last-name", [doe, smith]);
190
+ const fullName = new And("full-name", [firstName, space, lastName]);
191
+
192
+ const text = "Jack";
193
+ const autoComplete = new AutoComplete(fullName, autoCompleteOptions);
194
+ const { options, ast, errorAtIndex } = autoComplete.suggestFor(text);
195
+ const expectedOptions = [
196
+ { text: " Doe", startIndex: 4 },
197
+ { text: " Smith", startIndex: 4 },
198
+ { text: " Doe", startIndex: 4 },
199
+ { text: " Smith", startIndex: 4 },
200
+ ];
201
+
202
+ const results = expectedOptions.map(o => text.slice(0, o.startIndex) + o.text);
203
+ const expectedResults = [
204
+ "Jack Doe",
205
+ "Jack Smith",
206
+ "Jack Doe",
207
+ "Jack Smith",
208
+ ]
209
+
143
210
  expect(ast).toBeNull();
144
- expect(nextPatterns.length).toBe(1);
145
- expect(nextPatterns[0].name).toBe("space");
211
+ expect(errorAtIndex).toBe(4);
212
+ expect(options).toEqual(expectedOptions);
146
213
  expect(results).toEqual(expectedResults)
147
214
 
148
215
  });
149
216
 
217
+ test("Match On Different Pattern Roots", () => {
218
+ const start = new Literal("start", "John went to");
219
+ const a = new Literal("a", "a bank.");
220
+ const the = new Literal("the", "the store.");
221
+
222
+ const first = new And("first", [start, a]);
223
+ const second = new And("second", [start, the]);
224
+
225
+ const both = new Or("both", [first, second]);
226
+
227
+ const autoComplete = new AutoComplete(both);
228
+ const result = autoComplete.suggestFor("John went to a gas station.");
229
+ const expected = [
230
+ { text: "the store.", startIndex: 12 },
231
+ { text: "a bank.", startIndex: 12 }
232
+ ];
233
+ expect(result.options).toEqual(expected);
234
+ });
235
+
150
236
  });