@excom/quark-parser 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 (32) hide show
  1. package/.rush/temp/chunked-rush-logs/quark-parser.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/quark-parser.build_package-metas.chunks.jsonl +1 -0
  3. package/.rush/temp/operation/apply-exports/all.log +1 -0
  4. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  5. package/.rush/temp/operation/apply-exports/state.json +3 -0
  6. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  7. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  8. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +3 -0
  10. package/config/rig.json +5 -0
  11. package/index.ts +12 -0
  12. package/package.json +39 -0
  13. package/rush-logs/quark-parser.apply-exports.cache.log +1 -0
  14. package/rush-logs/quark-parser.apply-exports.log +1 -0
  15. package/rush-logs/quark-parser.build_package-metas.cache.log +1 -0
  16. package/rush-logs/quark-parser.build_package-metas.log +1 -0
  17. package/src/error.ts +24 -0
  18. package/src/parser.ts +1482 -0
  19. package/src/tables.ts +77 -0
  20. package/src/tokenizer.ts +443 -0
  21. package/src/types.ts +497 -0
  22. package/support/docs/README.md +443 -0
  23. package/support/package-meta.json +33 -0
  24. package/support/tests/grammar-docs.test.ts +109 -0
  25. package/support/tests/parser-at-rules.test.ts +430 -0
  26. package/support/tests/parser-declarations.test.ts +152 -0
  27. package/support/tests/parser-edge-cases.test.ts +296 -0
  28. package/support/tests/parser-expressions.test.ts +413 -0
  29. package/support/tests/parser-real-world.test.ts +429 -0
  30. package/support/tests/parser-selectors.test.ts +169 -0
  31. package/support/tests/tokenizer.test.ts +268 -0
  32. package/tsconfig.json +5 -0
@@ -0,0 +1,268 @@
1
+ import { tokenize } from "../../index";
2
+ import {
3
+ describe,
4
+ expect,
5
+ it,
6
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
7
+
8
+ const types = (src: string) => tokenize(src).tokens.map((t) => t.type);
9
+ const values = (src: string) => tokenize(src).tokens.map((t) => t.value);
10
+
11
+ describe("tokenizer", () => {
12
+ it("tokenizes identifiers, including hyphens and leading dashes", () => {
13
+ expect(values("provider-fetch data-me -webkit-box --custom")).toEqual([
14
+ "provider-fetch",
15
+ "data-me",
16
+ "-webkit-box",
17
+ "--custom",
18
+ ]);
19
+ expect(types("a b")).toEqual(["ident", "ident"]);
20
+ });
21
+
22
+ it("tokenizes variables (hyphens and digits allowed)", () => {
23
+ const { tokens } = tokenize("$currentUserId $max-users-2");
24
+ expect(tokens.map((t) => [t.type, t.value])).toEqual([
25
+ ["variable", "currentUserId"],
26
+ ["variable", "max-users-2"],
27
+ ]);
28
+ });
29
+
30
+ it("tokenizes at-keywords", () => {
31
+ const { tokens } = tokenize("@media @-webkit-keyframes");
32
+ expect(tokens.map((t) => [t.type, t.value])).toEqual([
33
+ ["at", "media"],
34
+ ["at", "-webkit-keyframes"],
35
+ ]);
36
+ });
37
+
38
+ it("tokenizes numbers with units and exponents", () => {
39
+ const { tokens } = tokenize("13 32.125 .5em 50% 2e3 2em");
40
+ expect(tokens.map((t) => [t.value, t.unit ?? null])).toEqual([
41
+ ["13", null],
42
+ ["32.125", null],
43
+ [".5", "em"],
44
+ ["50", "%"],
45
+ ["2e3", null],
46
+ ["2", "em"],
47
+ ]);
48
+ });
49
+
50
+ it("distinguishes negative numbers from subtraction", () => {
51
+ const withUnits = (src: string) =>
52
+ tokenize(src).tokens.map((t) => t.value + (t.unit ?? ""));
53
+ // expression start: sign
54
+ expect(withUnits(": -5")).toEqual([":", "-5"]);
55
+ // `10px -5px`: whitespace before, none after: sign (list shape)
56
+ expect(withUnits("10px -5px")).toEqual(["10px", "-5px"]);
57
+ // whitespace on both sides: subtraction
58
+ expect(withUnits("10 - 5")).toEqual(["10", "-", "5"]);
59
+ // no whitespace at all after a value: subtraction
60
+ expect(withUnits("10-5")).toEqual(["10", "-", "5"]);
61
+ });
62
+
63
+ it("tokenizes strings with escapes and preserves interpolation raw", () => {
64
+ const { tokens } = tokenize(`"a \\"b\\" c" 'd #{$e} f'`);
65
+ expect(tokens[0]).toMatchObject({
66
+ type: "string",
67
+ value: 'a \\"b\\" c',
68
+ quote: '"',
69
+ });
70
+ expect(tokens[1]).toMatchObject({
71
+ type: "string",
72
+ value: "d #{$e} f",
73
+ quote: "'",
74
+ });
75
+ });
76
+
77
+ it("does not end a string on a quote inside interpolation", () => {
78
+ const { tokens } = tokenize(`"a #{ "b" } c"`);
79
+ expect(tokens).toHaveLength(1);
80
+ expect(tokens[0].value).toBe(`a #{ "b" } c`);
81
+ });
82
+
83
+ it("separates comments from tokens", () => {
84
+ const { tokens, comments } = tokenize("a /* one */\n/* two */ b");
85
+ expect(tokens.map((t) => t.value)).toEqual(["a", "b"]);
86
+ expect(comments.map((c) => c.value)).toEqual([" one ", " two "]);
87
+ });
88
+
89
+ it("rejects line comments, pointing at the `//`", () => {
90
+ expect(() => tokenize("a {\n x: 1; // note\n}")).toThrow(
91
+ "Line comments are not supported, use /* */ (2:9)"
92
+ );
93
+ });
94
+
95
+ it("keeps `/` as division", () => {
96
+ expect(values("$a / 2")).toEqual(["a", "/", "2"]);
97
+ });
98
+
99
+ it("tokenizes hashes for ids and colors", () => {
100
+ const { tokens } = tokenize("#refresh-btn #fff");
101
+ expect(tokens.map((t) => [t.type, t.value])).toEqual([
102
+ ["hash", "refresh-btn"],
103
+ ["hash", "fff"],
104
+ ]);
105
+ });
106
+
107
+ it("tokenizes multi-character punctuation", () => {
108
+ expect(values("== != <= >= :: *= ~= ^= |= $= ... #{")).toEqual([
109
+ "==",
110
+ "!=",
111
+ "<=",
112
+ ">=",
113
+ "::",
114
+ "*=",
115
+ "~=",
116
+ "^=",
117
+ "|=",
118
+ "$=",
119
+ "...",
120
+ "#{",
121
+ ]);
122
+ });
123
+
124
+ it("tokenizes unquoted urls as raw", () => {
125
+ const { tokens } = tokenize("url(/img/kroger.png)");
126
+ expect(tokens.map((t) => [t.type, t.value])).toEqual([
127
+ ["ident", "url"],
128
+ ["punct", "("],
129
+ ["url", "/img/kroger.png"],
130
+ ["punct", ")"],
131
+ ]);
132
+ });
133
+
134
+ it("does not raw-scan quoted or variable urls", () => {
135
+ expect(types(`url("a.png")`)).toEqual(["ident", "punct", "string", "punct"]);
136
+ expect(types("url($src)")).toEqual(["ident", "punct", "variable", "punct"]);
137
+ });
138
+
139
+ it("handles urls containing what would otherwise be comments", () => {
140
+ const { tokens } = tokenize("url(http://example.com/a.png)");
141
+ expect(tokens[2]).toMatchObject({
142
+ type: "url",
143
+ value: "http://example.com/a.png",
144
+ });
145
+ });
146
+
147
+ it("sets the ws flag from preceding whitespace and comments", () => {
148
+ const { tokens } = tokenize("a b/* c */d");
149
+ expect(tokens.map((t) => [t.value, t.ws])).toEqual([
150
+ ["a", false],
151
+ ["b", true],
152
+ ["d", true],
153
+ ]);
154
+ });
155
+
156
+ it("records accurate spans", () => {
157
+ const src = "abc $def";
158
+ const { tokens } = tokenize(src);
159
+ expect(src.slice(tokens[0].start, tokens[0].end)).toBe("abc");
160
+ expect(src.slice(tokens[1].start, tokens[1].end)).toBe("$def");
161
+ });
162
+
163
+ it("throws on unterminated strings and comments", () => {
164
+ expect(() => tokenize('"abc')).toThrow(/Unterminated string/);
165
+ expect(() => tokenize("/* abc")).toThrow(/Unterminated comment/);
166
+ });
167
+
168
+ it("keeps backslash escapes inside identifiers", () => {
169
+ expect(values("a\\:b c")).toEqual(["a\\:b", "c"]);
170
+ });
171
+
172
+ it("tokenizes signed and multi-digit exponents", () => {
173
+ const { tokens } = tokenize("2e-3 1e10 1E+2 2e");
174
+ expect(tokens.map((t) => [t.value, t.unit ?? null])).toEqual([
175
+ ["2e-3", null],
176
+ ["1e10", null],
177
+ ["1E+2", null],
178
+ ["2", "e"],
179
+ ]);
180
+ });
181
+
182
+ it("tokenizes a signed leading-dot number", () => {
183
+ expect(values("-.5")).toEqual(["-.5"]);
184
+ expect(types("-.5")).toEqual(["number"]);
185
+ });
186
+
187
+ it("emits lone $, @, and # as punctuation", () => {
188
+ expect(tokenize("$ @ # x").tokens.map((t) => [t.type, t.value])).toEqual([
189
+ ["punct", "$"],
190
+ ["punct", "@"],
191
+ ["punct", "#"],
192
+ ["ident", "x"],
193
+ ]);
194
+ });
195
+
196
+ it("tokenizes single-character forms of the compound operators", () => {
197
+ expect(values("= ! < > : * ~ ^ |")).toEqual([
198
+ "=",
199
+ "!",
200
+ "<",
201
+ ">",
202
+ ":",
203
+ "*",
204
+ "~",
205
+ "^",
206
+ "|",
207
+ ]);
208
+ });
209
+
210
+ it("does not end a string on a quote or brace nested in interpolation", () => {
211
+ expect(tokenize('"#{ {} }"').tokens).toHaveLength(1);
212
+ const escaped = tokenize('"#{ "a\\"b" }"').tokens;
213
+ expect(escaped).toHaveLength(1);
214
+ expect(escaped[0].value).toBe('#{ "a\\"b" }');
215
+ });
216
+
217
+ it("trims whitespace around raw url contents", () => {
218
+ const { tokens } = tokenize("url( /a.png )");
219
+ expect(tokens[2]).toMatchObject({ type: "url", value: "/a.png" });
220
+ expect(tokens[3]).toMatchObject({ type: "punct", value: ")" });
221
+ });
222
+
223
+ it("keeps escapes and interpolation inside raw urls", () => {
224
+ expect(tokenize("url(a\\)b.png)").tokens[2]).toMatchObject({
225
+ type: "url",
226
+ value: "a\\)b.png",
227
+ });
228
+ expect(tokenize("url(/img/#{$n}.svg)").tokens[2]).toMatchObject({
229
+ type: "url",
230
+ value: "/img/#{$n}.svg",
231
+ });
232
+ });
233
+
234
+ it("falls back to normal tokens when url contents are not raw", () => {
235
+ // internal whitespace, nested parens, quotes, variables, unterminated
236
+ expect(types("url(a b)")).toEqual([
237
+ "ident",
238
+ "punct",
239
+ "ident",
240
+ "ident",
241
+ "punct",
242
+ ]);
243
+ expect(types("url(a(b))")).toEqual([
244
+ "ident",
245
+ "punct",
246
+ "ident",
247
+ "punct",
248
+ "ident",
249
+ "punct",
250
+ "punct",
251
+ ]);
252
+ expect(types('url(a"b")')).toEqual([
253
+ "ident",
254
+ "punct",
255
+ "ident",
256
+ "string",
257
+ "punct",
258
+ ]);
259
+ expect(types("url(a$b)")).toEqual([
260
+ "ident",
261
+ "punct",
262
+ "ident",
263
+ "variable",
264
+ "punct",
265
+ ]);
266
+ expect(types("url(abc")).toEqual(["ident", "punct", "ident"]);
267
+ });
268
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "@excom/heft-rig/profiles/default/config/tsconfig.json",
3
+ "include": ["./*.ts", "./src/**/*.ts"],
4
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
5
+ }