@markuplint/types 3.0.0-alpha.0 → 3.0.0-alpha.2
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.
- package/README.md +1 -1
- package/lib/css-syntax.spec.js +1 -1
- package/lib/defs.js +4 -4
- package/lib/get-candidate.d.ts +3 -0
- package/lib/{get-candicate.js → get-candidate.js} +8 -8
- package/lib/list.spec.js +4 -4
- package/lib/number.js +4 -4
- package/lib/primitive/is-uint.d.ts +1 -1
- package/lib/primitive/is-uint.js +4 -4
- package/lib/token/token-collection.d.ts +21 -21
- package/lib/token/token-collection.js +101 -101
- package/lib/token/token-collection.spec.js +2 -2
- package/lib/token/token.d.ts +22 -22
- package/lib/token/token.js +36 -36
- package/lib/types.d.ts +2 -2
- package/lib/types.schema.d.ts +3 -0
- package/lib/whatwg/check-autocomplete.js +55 -15
- package/lib/whatwg/check-autocomplete.spec.js +19 -10
- package/lib/whatwg/check-datetime/datetime-tokens.d.ts +1 -1
- package/lib/whatwg/check-datetime/datetime-tokens.js +16 -16
- package/lib/whatwg/check-datetime/global-date-and-time-string.js +3 -3
- package/lib/whatwg/check-datetime/local-date-and-time-string.js +2 -2
- package/lib/whatwg/check-datetime/time-string.js +1 -1
- package/lib/whatwg/check-datetime/time-zone-offset-string.js +4 -4
- package/lib/whatwg/check-mime-type.js +1 -1
- package/lib/whatwg/check-mime-type.spec.js +3 -3
- package/package.json +4 -4
- package/src/css-syntax.spec.ts +1 -1
- package/src/defs.ts +4 -4
- package/src/{get-candicate.ts → get-candidate.ts} +6 -6
- package/src/list.spec.ts +4 -4
- package/src/number.ts +4 -4
- package/src/primitive/is-uint.ts +4 -4
- package/src/token/token-collection.spec.ts +2 -2
- package/src/token/token-collection.ts +113 -113
- package/src/token/token.ts +58 -58
- package/src/types.schema.ts +3 -0
- package/src/types.ts +2 -2
- package/src/whatwg/check-autocomplete.spec.ts +19 -10
- package/src/whatwg/check-autocomplete.ts +71 -15
- package/src/whatwg/check-datetime/datetime-tokens.ts +18 -18
- package/src/whatwg/check-datetime/global-date-and-time-string.ts +3 -3
- package/src/whatwg/check-datetime/local-date-and-time-string.ts +4 -4
- package/src/whatwg/check-datetime/time-string.ts +2 -2
- package/src/whatwg/check-datetime/time-zone-offset-string.ts +4 -4
- package/src/whatwg/check-mime-type.spec.ts +3 -3
- package/src/whatwg/check-mime-type.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/lib/get-candicate.d.ts +0 -3
package/src/css-syntax.spec.ts
CHANGED
|
@@ -116,7 +116,7 @@ test('custom-type-checking', () => {
|
|
|
116
116
|
expect(cssSyntaxMatch('0', custom).matched).toBe(false);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
test('case-
|
|
119
|
+
test('case-sensitive', () => {
|
|
120
120
|
const custom: CustomCssSyntax = {
|
|
121
121
|
ref: 'n/a',
|
|
122
122
|
caseSensitive: true,
|
package/src/defs.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Defs, CssSyntaxTokenizer } from './types';
|
|
2
2
|
|
|
3
3
|
import { checkMultiTypes } from './check-multi-types';
|
|
4
|
-
import {
|
|
4
|
+
import { getCandidate } from './get-candidate';
|
|
5
5
|
import { matched, matches, unmatched } from './match-result';
|
|
6
6
|
import { splitUnit, isFloat, isUint, isInt } from './primitive';
|
|
7
7
|
import { isBCP47 } from './rfc/is-bcp-47';
|
|
@@ -295,8 +295,8 @@ export const types: Defs = {
|
|
|
295
295
|
return matched();
|
|
296
296
|
}
|
|
297
297
|
if (value[0] === '_') {
|
|
298
|
-
const
|
|
299
|
-
return unmatched(value, 'unexpected-token', {
|
|
298
|
+
const candidate = getCandidate(value, keywords);
|
|
299
|
+
return unmatched(value, 'unexpected-token', { candidate });
|
|
300
300
|
}
|
|
301
301
|
return matches(isBrowserContextName())(value);
|
|
302
302
|
},
|
|
@@ -446,7 +446,7 @@ export const types: Defs = {
|
|
|
446
446
|
if (value === 'any') {
|
|
447
447
|
return matched();
|
|
448
448
|
}
|
|
449
|
-
const tokens = new TokenCollection(value, {
|
|
449
|
+
const tokens = new TokenCollection(value, { specificSeparator: 'x' });
|
|
450
450
|
const [width, sep, height, ...tail] = tokens;
|
|
451
451
|
if (!width) {
|
|
452
452
|
return unmatched(value, 'unexpected-token');
|
|
@@ -2,23 +2,23 @@ import leven from 'leven';
|
|
|
2
2
|
|
|
3
3
|
type NullableString = string | null | undefined;
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function getCandidate(value: NullableString, ...candidates: (NullableString | NullableString[])[]) {
|
|
6
6
|
if (!value) {
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
|
-
const list =
|
|
10
|
-
let
|
|
9
|
+
const list = candidates.flat(2).filter((s): s is string => !!s);
|
|
10
|
+
let candidate: string | undefined;
|
|
11
11
|
let maxRatio = 0;
|
|
12
12
|
list.forEach(word => {
|
|
13
13
|
const dist = leven(value.trim().toLowerCase(), word.trim().toLowerCase());
|
|
14
14
|
const ratio = 1 - dist / word.length;
|
|
15
15
|
if (0.5 <= ratio && maxRatio < ratio) {
|
|
16
|
-
|
|
16
|
+
candidate = word;
|
|
17
17
|
}
|
|
18
18
|
maxRatio = Math.max(ratio, maxRatio);
|
|
19
19
|
});
|
|
20
|
-
if (value ===
|
|
20
|
+
if (value === candidate) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
-
return
|
|
23
|
+
return candidate;
|
|
24
24
|
}
|
package/src/list.spec.ts
CHANGED
|
@@ -50,7 +50,7 @@ test('Zero comma (disallowToSurroundBySpaces)', () => {
|
|
|
50
50
|
test('Location of the token', () => {
|
|
51
51
|
const type = { token: 'Accept', separator: 'comma' } as const;
|
|
52
52
|
expect(checkList('x/y;a=b', type)).toStrictEqual({
|
|
53
|
-
|
|
53
|
+
candidate: 'x/y',
|
|
54
54
|
column: 4,
|
|
55
55
|
expects: [{ type: 'format', value: 'MIME Type with no parameters' }],
|
|
56
56
|
length: 4,
|
|
@@ -63,7 +63,7 @@ test('Location of the token', () => {
|
|
|
63
63
|
ref: 'https://html.spec.whatwg.org/multipage/input.html#attr-input-accept',
|
|
64
64
|
});
|
|
65
65
|
expect(checkList('x/y,x/y;a=b', type)).toStrictEqual({
|
|
66
|
-
|
|
66
|
+
candidate: 'x/y',
|
|
67
67
|
column: 8,
|
|
68
68
|
expects: [{ type: 'format', value: 'MIME Type with no parameters' }],
|
|
69
69
|
length: 4,
|
|
@@ -76,7 +76,7 @@ test('Location of the token', () => {
|
|
|
76
76
|
ref: 'https://html.spec.whatwg.org/multipage/input.html#attr-input-accept',
|
|
77
77
|
});
|
|
78
78
|
expect(checkList('x/y\n,\nx/y;a=b', type)).toStrictEqual({
|
|
79
|
-
|
|
79
|
+
candidate: 'x/y',
|
|
80
80
|
column: 4,
|
|
81
81
|
expects: [{ type: 'format', value: 'MIME Type with no parameters' }],
|
|
82
82
|
length: 4,
|
|
@@ -120,7 +120,7 @@ test('Missing comma', () => {
|
|
|
120
120
|
const type = { token: 'Accept', separator: 'comma' } as const;
|
|
121
121
|
expect(checkList('a/b,x/y a/z', type)).toStrictEqual({
|
|
122
122
|
column: 9,
|
|
123
|
-
|
|
123
|
+
candidate: ',a/z',
|
|
124
124
|
expects: undefined,
|
|
125
125
|
length: 3,
|
|
126
126
|
line: 1,
|
package/src/number.ts
CHANGED
|
@@ -20,25 +20,25 @@ export function checkNumber(value: string, type: Number, ref?: string): Result {
|
|
|
20
20
|
|
|
21
21
|
if (type.gt != null && n <= type.gt) {
|
|
22
22
|
return unmatched(value, 'out-of-range', {
|
|
23
|
-
|
|
23
|
+
candidate: clampable && isInt ? `${type.gt + 1}` : undefined,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (type.gte != null && n < type.gte) {
|
|
28
28
|
return unmatched(value, 'out-of-range', {
|
|
29
|
-
|
|
29
|
+
candidate: clampable ? `${type.gte}` : undefined,
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
if (type.lt != null && type.lt <= n) {
|
|
34
34
|
return unmatched(value, 'out-of-range', {
|
|
35
|
-
|
|
35
|
+
candidate: clampable && isInt ? `${type.lt - 1}` : undefined,
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (type.lte != null && type.lte < n) {
|
|
40
40
|
return unmatched(value, 'out-of-range', {
|
|
41
|
-
|
|
41
|
+
candidate: clampable ? `${type.lte}` : undefined,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
}
|
package/src/primitive/is-uint.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @param value
|
|
5
5
|
*/
|
|
6
|
-
export function isUint(value: string,
|
|
6
|
+
export function isUint(value: string, options?: { gt?: number }) {
|
|
7
7
|
const matched = /^[0-9]+$/.test(value);
|
|
8
|
-
if (matched &&
|
|
8
|
+
if (matched && options) {
|
|
9
9
|
const n = parseInt(value, 10);
|
|
10
|
-
if (
|
|
11
|
-
return isFinite(n) &&
|
|
10
|
+
if (options.gt != null) {
|
|
11
|
+
return isFinite(n) && options.gt < n;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
return matched;
|
|
@@ -42,7 +42,7 @@ test('parse', () => {
|
|
|
42
42
|
]);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
test('parse
|
|
45
|
+
test('parse comma separator', () => {
|
|
46
46
|
expect(new TokenCollection('a,, ,b,cde', { separator: 'comma' }).toJSON()).toStrictEqual([
|
|
47
47
|
{
|
|
48
48
|
offset: 0,
|
|
@@ -87,7 +87,7 @@ test('parse camma separator', () => {
|
|
|
87
87
|
]);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
test('A
|
|
90
|
+
test('A comma is an ident if the comma is not a separator', () => {
|
|
91
91
|
expect(new TokenCollection('a ,cde , f').toJSON()).toStrictEqual([
|
|
92
92
|
{
|
|
93
93
|
offset: 0,
|
|
@@ -7,23 +7,17 @@ import { Token } from './token';
|
|
|
7
7
|
|
|
8
8
|
type TokenCollectionOptions = Partial<
|
|
9
9
|
Omit<List, 'token'> & {
|
|
10
|
-
|
|
10
|
+
specificSeparator: string | string[];
|
|
11
11
|
}
|
|
12
12
|
>;
|
|
13
13
|
|
|
14
14
|
export type TokenEachCheck = (head: Token | null, tail: TokenCollection) => Result | void;
|
|
15
15
|
|
|
16
16
|
export class TokenCollection extends Array<Token> {
|
|
17
|
-
private static _new(tokens: Token[], old?: TokenCollection) {
|
|
18
|
-
const newCollection = new TokenCollection('', old);
|
|
19
|
-
newCollection.push(...tokens);
|
|
20
|
-
return newCollection;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
static fromPatterns(
|
|
24
18
|
value: Token | string,
|
|
25
19
|
patterns: RegExp[],
|
|
26
|
-
typeOptions?: Omit<TokenCollectionOptions, '
|
|
20
|
+
typeOptions?: Omit<TokenCollectionOptions, 'specificSeparator'> & { repeat?: boolean },
|
|
27
21
|
) {
|
|
28
22
|
const origin = typeof value === 'string' ? value : value.origin;
|
|
29
23
|
let strings = typeof value === 'string' ? value : value.value;
|
|
@@ -40,15 +34,15 @@ export class TokenCollection extends Array<Token> {
|
|
|
40
34
|
return token;
|
|
41
35
|
}
|
|
42
36
|
|
|
43
|
-
let
|
|
37
|
+
let isBroken = false;
|
|
44
38
|
do {
|
|
45
|
-
|
|
39
|
+
isBroken = false;
|
|
46
40
|
for (const pattern of patterns) {
|
|
47
41
|
const res = pattern.exec(strings);
|
|
48
42
|
let value: string;
|
|
49
43
|
|
|
50
44
|
if (!res) {
|
|
51
|
-
|
|
45
|
+
isBroken = true;
|
|
52
46
|
value = '';
|
|
53
47
|
} else {
|
|
54
48
|
if (res.index !== 0) {
|
|
@@ -63,7 +57,7 @@ export class TokenCollection extends Array<Token> {
|
|
|
63
57
|
// @ts-ignore
|
|
64
58
|
token._ = pattern;
|
|
65
59
|
}
|
|
66
|
-
if (
|
|
60
|
+
if (isBroken) {
|
|
67
61
|
addToken(strings);
|
|
68
62
|
}
|
|
69
63
|
} while (typeOptions?.repeat && strings);
|
|
@@ -79,13 +73,13 @@ export class TokenCollection extends Array<Token> {
|
|
|
79
73
|
return Array;
|
|
80
74
|
}
|
|
81
75
|
|
|
82
|
-
readonly disallowToSurroundBySpaces: NonNullable<List['disallowToSurroundBySpaces']>;
|
|
83
76
|
readonly allowEmpty: NonNullable<List['allowEmpty']>;
|
|
84
|
-
readonly ordered: NonNullable<List['ordered']>;
|
|
85
|
-
readonly unique: NonNullable<List['unique']>;
|
|
86
77
|
readonly caseInsensitive: NonNullable<List['caseInsensitive']>;
|
|
78
|
+
readonly disallowToSurroundBySpaces: NonNullable<List['disallowToSurroundBySpaces']>;
|
|
87
79
|
readonly number: List['number'];
|
|
80
|
+
readonly ordered: NonNullable<List['ordered']>;
|
|
88
81
|
readonly separator: NonNullable<List['separator']>;
|
|
82
|
+
readonly unique: NonNullable<List['unique']>;
|
|
89
83
|
|
|
90
84
|
constructor(value?: string, typeOptions?: TokenCollectionOptions);
|
|
91
85
|
constructor(value?: number); // for map method etc.
|
|
@@ -114,11 +108,11 @@ export class TokenCollection extends Array<Token> {
|
|
|
114
108
|
separators.push(',');
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
if (typeOptions?.
|
|
118
|
-
if (Array.isArray(typeOptions.
|
|
119
|
-
separators.push(...typeOptions.
|
|
111
|
+
if (typeOptions?.specificSeparator) {
|
|
112
|
+
if (Array.isArray(typeOptions.specificSeparator)) {
|
|
113
|
+
separators.push(...typeOptions.specificSeparator);
|
|
120
114
|
} else {
|
|
121
|
-
separators.push(typeOptions.
|
|
115
|
+
separators.push(typeOptions.specificSeparator);
|
|
122
116
|
}
|
|
123
117
|
}
|
|
124
118
|
|
|
@@ -158,62 +152,6 @@ export class TokenCollection extends Array<Token> {
|
|
|
158
152
|
return value;
|
|
159
153
|
}
|
|
160
154
|
|
|
161
|
-
filter(callback: (value: Token, index: number, array: Token[]) => boolean) {
|
|
162
|
-
return TokenCollection._new(super.filter(callback), this);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
headAndTail(): { head: Token | null; tail: TokenCollection } {
|
|
166
|
-
const copy = this.slice();
|
|
167
|
-
const head = copy.shift();
|
|
168
|
-
if (!head) {
|
|
169
|
-
return { head: head || null, tail: TokenCollection._new([], this) };
|
|
170
|
-
}
|
|
171
|
-
const tail = TokenCollection._new(copy, this);
|
|
172
|
-
return { head, tail };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
getIdentTokens() {
|
|
176
|
-
return this.filter(token => token.type === Token.Ident);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
compareTokens(callback: (prev: Token, current: Token) => Token | null | void) {
|
|
180
|
-
const _tokens = this.slice();
|
|
181
|
-
let prev = _tokens.shift();
|
|
182
|
-
while (prev) {
|
|
183
|
-
const current = _tokens.shift();
|
|
184
|
-
if (!current) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
const result = callback(prev, current);
|
|
188
|
-
if (result) {
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
prev = current;
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
*
|
|
198
|
-
* @param value The token value or the token type or its list
|
|
199
|
-
*/
|
|
200
|
-
has(value: string | RegExp | number | (string | RegExp | number)[]) {
|
|
201
|
-
return this.some(t => t.match(value));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
*
|
|
206
|
-
* @param value The token value or the token type or its list
|
|
207
|
-
*/
|
|
208
|
-
search(value: string | RegExp | number | (string | RegExp | number)[]) {
|
|
209
|
-
for (const token of this) {
|
|
210
|
-
if (token.includes(value)) {
|
|
211
|
-
return token;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
155
|
check(options: { expects?: Expect[]; ref?: string; cache?: boolean } = {}) {
|
|
218
156
|
const { expects, ref } = options;
|
|
219
157
|
|
|
@@ -268,7 +206,7 @@ export class TokenCollection extends Array<Token> {
|
|
|
268
206
|
reason: 'missing-comma',
|
|
269
207
|
ref,
|
|
270
208
|
expects,
|
|
271
|
-
|
|
209
|
+
candidate: `,${takeTurnsError.token.value}`,
|
|
272
210
|
});
|
|
273
211
|
}
|
|
274
212
|
}
|
|
@@ -298,38 +236,42 @@ export class TokenCollection extends Array<Token> {
|
|
|
298
236
|
return matched();
|
|
299
237
|
}
|
|
300
238
|
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
239
|
+
chunk(split: number) {
|
|
240
|
+
const chunks: TokenCollection[] = [];
|
|
241
|
+
const tokens = this.slice();
|
|
242
|
+
while (tokens.length) {
|
|
243
|
+
const chunkTokens = tokens.splice(0, split);
|
|
244
|
+
const chunk = TokenCollection._new(chunkTokens, this);
|
|
245
|
+
chunks.push(chunk);
|
|
246
|
+
}
|
|
247
|
+
return chunks;
|
|
308
248
|
}
|
|
309
249
|
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
if (
|
|
316
|
-
return
|
|
317
|
-
unexpectedLastToken: false,
|
|
318
|
-
expectedTokenNumber,
|
|
319
|
-
token,
|
|
320
|
-
};
|
|
250
|
+
compareTokens(callback: (prev: Token, current: Token) => Token | null | void) {
|
|
251
|
+
const _tokens = this.slice();
|
|
252
|
+
let prev = _tokens.shift();
|
|
253
|
+
while (prev) {
|
|
254
|
+
const current = _tokens.shift();
|
|
255
|
+
if (!current) {
|
|
256
|
+
return;
|
|
321
257
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
expectedTokenNumber,
|
|
326
|
-
token,
|
|
327
|
-
};
|
|
258
|
+
const result = callback(prev, current);
|
|
259
|
+
if (result) {
|
|
260
|
+
return result;
|
|
328
261
|
}
|
|
262
|
+
prev = current;
|
|
329
263
|
}
|
|
330
264
|
return null;
|
|
331
265
|
}
|
|
332
266
|
|
|
267
|
+
divide(position: number) {
|
|
268
|
+
const _a = this.slice(0, position);
|
|
269
|
+
const _b = this.slice(position);
|
|
270
|
+
const a = TokenCollection._new(_a, this);
|
|
271
|
+
const b = TokenCollection._new(_b, this);
|
|
272
|
+
return [a, b] as const;
|
|
273
|
+
}
|
|
274
|
+
|
|
333
275
|
eachCheck(...callbacks: TokenEachCheck[]): Result {
|
|
334
276
|
let headAndTail = this.headAndTail();
|
|
335
277
|
let head = headAndTail.head;
|
|
@@ -397,6 +339,19 @@ export class TokenCollection extends Array<Token> {
|
|
|
397
339
|
return matched();
|
|
398
340
|
}
|
|
399
341
|
|
|
342
|
+
filter(callback: (value: Token, index: number, array: Token[]) => boolean) {
|
|
343
|
+
return TokenCollection._new(super.filter(callback), this);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
getConsecutiveToken(tokenType: number) {
|
|
347
|
+
const resultToken = this.compareTokens((prev, current) => {
|
|
348
|
+
if (prev.type === tokenType && current.type === tokenType) {
|
|
349
|
+
return current;
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return resultToken || null;
|
|
353
|
+
}
|
|
354
|
+
|
|
400
355
|
getDuplicated() {
|
|
401
356
|
const aList = this.slice();
|
|
402
357
|
const bList = this.slice();
|
|
@@ -419,26 +374,71 @@ export class TokenCollection extends Array<Token> {
|
|
|
419
374
|
return null;
|
|
420
375
|
}
|
|
421
376
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const _b = this.slice(position);
|
|
425
|
-
const a = TokenCollection._new(_a, this);
|
|
426
|
-
const b = TokenCollection._new(_b, this);
|
|
427
|
-
return [a, b] as const;
|
|
377
|
+
getIdentTokens() {
|
|
378
|
+
return this.filter(token => token.type === Token.Ident);
|
|
428
379
|
}
|
|
429
380
|
|
|
430
|
-
|
|
431
|
-
|
|
381
|
+
/**
|
|
382
|
+
*
|
|
383
|
+
* @param value The token value or the token type or its list
|
|
384
|
+
*/
|
|
385
|
+
has(value: string | RegExp | number | (string | RegExp | number)[]) {
|
|
386
|
+
return this.some(t => t.match(value));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
headAndTail(): { head: Token | null; tail: TokenCollection } {
|
|
390
|
+
const copy = this.slice();
|
|
391
|
+
const head = copy.shift();
|
|
392
|
+
if (!head) {
|
|
393
|
+
return { head: head || null, tail: TokenCollection._new([], this) };
|
|
394
|
+
}
|
|
395
|
+
const tail = TokenCollection._new(copy, this);
|
|
396
|
+
return { head, tail };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
*
|
|
401
|
+
* @param value The token value or the token type or its list
|
|
402
|
+
*/
|
|
403
|
+
search(value: string | RegExp | number | (string | RegExp | number)[]) {
|
|
404
|
+
for (const token of this) {
|
|
405
|
+
if (token.includes(value)) {
|
|
406
|
+
return token;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
takeTurns(tokenNumbers: ReadonlyArray<number>, lastTokenNumber?: number) {
|
|
432
413
|
const tokens = this.slice();
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
const
|
|
436
|
-
|
|
414
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
415
|
+
const token = tokens[i];
|
|
416
|
+
const expectedTokenNumber = tokenNumbers[i % tokenNumbers.length];
|
|
417
|
+
if (token.type !== expectedTokenNumber) {
|
|
418
|
+
return {
|
|
419
|
+
unexpectedLastToken: false,
|
|
420
|
+
expectedTokenNumber,
|
|
421
|
+
token,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (lastTokenNumber != null && i === tokens.length - 1 && token.type !== lastTokenNumber) {
|
|
425
|
+
return {
|
|
426
|
+
unexpectedLastToken: true,
|
|
427
|
+
expectedTokenNumber,
|
|
428
|
+
token,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
437
431
|
}
|
|
438
|
-
return
|
|
432
|
+
return null;
|
|
439
433
|
}
|
|
440
434
|
|
|
441
435
|
toJSON() {
|
|
442
436
|
return this.map(t => t.toJSON());
|
|
443
437
|
}
|
|
438
|
+
|
|
439
|
+
private static _new(tokens: Token[], old?: TokenCollection) {
|
|
440
|
+
const newCollection = new TokenCollection('', old);
|
|
441
|
+
newCollection.push(...tokens);
|
|
442
|
+
return newCollection;
|
|
443
|
+
}
|
|
444
444
|
}
|
package/src/token/token.ts
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import type { UnmatchedResult, UnmatchedResultOptions, UnmatchedResultReason } from '../types';
|
|
2
2
|
|
|
3
3
|
export class Token {
|
|
4
|
+
/**
|
|
5
|
+
* @see https://github.com/csstree/csstree/blob/master/lib/tokenizer/types.js
|
|
6
|
+
*/
|
|
7
|
+
static readonly Comma = 18;
|
|
8
|
+
static readonly Ident = 1;
|
|
9
|
+
static readonly WhiteSpace = 13;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ASCII whitespace is
|
|
13
|
+
* - U+0009 TAB
|
|
14
|
+
* - U+000A LF
|
|
15
|
+
* - U+000C FF
|
|
16
|
+
* - U+000D CR
|
|
17
|
+
* - U+0020 SPACE.
|
|
18
|
+
*
|
|
19
|
+
* @see https://infra.spec.whatwg.org/#ascii-whitespace
|
|
20
|
+
*/
|
|
21
|
+
static readonly whitespace: ReadonlyArray<string> = ['\u0009', '\u000A', '\u000C', '\u000D', '\u0020'];
|
|
22
|
+
|
|
23
|
+
static getCol(value: string, offset: number) {
|
|
24
|
+
const lines = value.slice(0, offset).split(/\n/g);
|
|
25
|
+
return lines[lines.length - 1].length + 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static getLine(value: string, offset: number) {
|
|
29
|
+
return value.slice(0, offset).split(/\n/g).length;
|
|
30
|
+
}
|
|
31
|
+
|
|
4
32
|
static getType(value: string, separators?: string[]) {
|
|
5
33
|
if (Token.whitespace.includes(value[0])) {
|
|
6
34
|
return Token.WhiteSpace;
|
|
@@ -14,15 +42,6 @@ export class Token {
|
|
|
14
42
|
return Token.Ident;
|
|
15
43
|
}
|
|
16
44
|
|
|
17
|
-
static getLine(value: string, offset: number) {
|
|
18
|
-
return value.slice(0, offset).split(/\n/g).length;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
static getCol(value: string, offset: number) {
|
|
22
|
-
const lines = value.slice(0, offset).split(/\n/g);
|
|
23
|
-
return lines[lines.length - 1].length + 1;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
45
|
static shiftLocation(token: Token, offset: number) {
|
|
27
46
|
const shifted = token.offset + offset;
|
|
28
47
|
return {
|
|
@@ -32,29 +51,10 @@ export class Token {
|
|
|
32
51
|
};
|
|
33
52
|
}
|
|
34
53
|
|
|
35
|
-
/**
|
|
36
|
-
* ASCII whitespace is
|
|
37
|
-
* - U+0009 TAB
|
|
38
|
-
* - U+000A LF
|
|
39
|
-
* - U+000C FF
|
|
40
|
-
* - U+000D CR
|
|
41
|
-
* - U+0020 SPACE.
|
|
42
|
-
*
|
|
43
|
-
* @see https://infra.spec.whatwg.org/#ascii-whitespace
|
|
44
|
-
*/
|
|
45
|
-
static readonly whitespace: ReadonlyArray<string> = ['\u0009', '\u000A', '\u000C', '\u000D', '\u0020'];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @see https://github.com/csstree/csstree/blob/master/lib/tokenizer/types.js
|
|
49
|
-
*/
|
|
50
|
-
static readonly Ident = 1;
|
|
51
|
-
static readonly WhiteSpace = 13;
|
|
52
|
-
static readonly Comma = 18;
|
|
53
|
-
|
|
54
|
-
readonly type: number;
|
|
55
|
-
readonly value: string;
|
|
56
54
|
readonly offset: number;
|
|
57
55
|
#originalValue: string;
|
|
56
|
+
readonly type: number;
|
|
57
|
+
readonly value: string;
|
|
58
58
|
|
|
59
59
|
constructor(value: string, offset: number, originalValue: string, separators?: string[]) {
|
|
60
60
|
this.type = Token.getType(value, separators);
|
|
@@ -71,37 +71,18 @@ export class Token {
|
|
|
71
71
|
return this.#originalValue;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
unmatched(
|
|
75
|
-
options?: UnmatchedResultOptions & {
|
|
76
|
-
ref?: string;
|
|
77
|
-
reason?: UnmatchedResultReason;
|
|
78
|
-
},
|
|
79
|
-
): UnmatchedResult {
|
|
80
|
-
return {
|
|
81
|
-
...options,
|
|
82
|
-
matched: false,
|
|
83
|
-
ref: options?.ref || null,
|
|
84
|
-
raw: this.value,
|
|
85
|
-
offset: this.offset,
|
|
86
|
-
length: this.value.length,
|
|
87
|
-
line: Token.getLine(this.#originalValue, this.offset),
|
|
88
|
-
column: Token.getCol(this.#originalValue, this.offset),
|
|
89
|
-
reason: options?.reason ?? 'syntax-error',
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
74
|
/**
|
|
94
75
|
*
|
|
95
76
|
* @param value The token value or the token type or its list
|
|
96
77
|
*/
|
|
97
|
-
|
|
78
|
+
includes(value: string | RegExp | number | (string | RegExp | number)[], caseInsensitive?: boolean): boolean {
|
|
98
79
|
if (Array.isArray(value)) {
|
|
99
|
-
return value.some(v => this.
|
|
80
|
+
return value.some(v => this.includes(v));
|
|
100
81
|
}
|
|
101
82
|
if (typeof value === 'string') {
|
|
102
83
|
const a = caseInsensitive ? this.value.toLowerCase() : this.value;
|
|
103
84
|
const b = caseInsensitive ? value.toLowerCase() : value;
|
|
104
|
-
return a
|
|
85
|
+
return a.indexOf(b) !== -1;
|
|
105
86
|
}
|
|
106
87
|
if (value instanceof RegExp) {
|
|
107
88
|
const pattern = new RegExp(value, caseInsensitive ? 'i' : '');
|
|
@@ -114,14 +95,14 @@ export class Token {
|
|
|
114
95
|
*
|
|
115
96
|
* @param value The token value or the token type or its list
|
|
116
97
|
*/
|
|
117
|
-
|
|
98
|
+
match(value: string | RegExp | number | (string | RegExp | number)[], caseInsensitive?: boolean): boolean {
|
|
118
99
|
if (Array.isArray(value)) {
|
|
119
|
-
return value.some(v => this.
|
|
100
|
+
return value.some(v => this.match(v));
|
|
120
101
|
}
|
|
121
102
|
if (typeof value === 'string') {
|
|
122
103
|
const a = caseInsensitive ? this.value.toLowerCase() : this.value;
|
|
123
104
|
const b = caseInsensitive ? value.toLowerCase() : value;
|
|
124
|
-
return a
|
|
105
|
+
return a === b;
|
|
125
106
|
}
|
|
126
107
|
if (value instanceof RegExp) {
|
|
127
108
|
const pattern = new RegExp(value, caseInsensitive ? 'i' : '');
|
|
@@ -130,15 +111,34 @@ export class Token {
|
|
|
130
111
|
return this.type === value;
|
|
131
112
|
}
|
|
132
113
|
|
|
114
|
+
toJSON() {
|
|
115
|
+
return {
|
|
116
|
+
type: this.type,
|
|
117
|
+
value: this.value,
|
|
118
|
+
offset: this.offset,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
133
122
|
toNumber() {
|
|
134
123
|
return parseFloat(this.value);
|
|
135
124
|
}
|
|
136
125
|
|
|
137
|
-
|
|
126
|
+
unmatched(
|
|
127
|
+
options?: UnmatchedResultOptions & {
|
|
128
|
+
ref?: string;
|
|
129
|
+
reason?: UnmatchedResultReason;
|
|
130
|
+
},
|
|
131
|
+
): UnmatchedResult {
|
|
138
132
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
...options,
|
|
134
|
+
matched: false,
|
|
135
|
+
ref: options?.ref || null,
|
|
136
|
+
raw: this.value,
|
|
141
137
|
offset: this.offset,
|
|
138
|
+
length: this.value.length,
|
|
139
|
+
line: Token.getLine(this.#originalValue, this.offset),
|
|
140
|
+
column: Token.getCol(this.#originalValue, this.offset),
|
|
141
|
+
reason: options?.reason ?? 'syntax-error',
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
}
|
package/src/types.schema.ts
CHANGED
|
@@ -1037,6 +1037,9 @@ export interface List {
|
|
|
1037
1037
|
* [Enumerated attributes](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#enumerated-attribute)
|
|
1038
1038
|
*/
|
|
1039
1039
|
export interface Enum {
|
|
1040
|
+
/**
|
|
1041
|
+
* @minItems 1
|
|
1042
|
+
*/
|
|
1040
1043
|
enum: [string, ...string[]];
|
|
1041
1044
|
disallowToSurroundBySpaces?: boolean;
|
|
1042
1045
|
caseInsensitive?: boolean;
|