@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.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.
- package/dist/index.d.ts +39 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -512
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/build/index.ts +0 -209
- package/src/config.ts +0 -304
- package/src/index.ts +0 -95
- package/src/lib/code-frame.ts +0 -177
- package/src/lib/momoa.ts +0 -10
- package/src/lib/resolver-utils.ts +0 -35
- package/src/lint/index.ts +0 -142
- package/src/lint/plugin-core/index.ts +0 -103
- package/src/lint/plugin-core/lib/docs.ts +0 -3
- package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
- package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
- package/src/lint/plugin-core/rules/colorspace.ts +0 -108
- package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
- package/src/lint/plugin-core/rules/descriptions.ts +0 -43
- package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
- package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
- package/src/lint/plugin-core/rules/required-children.ts +0 -106
- package/src/lint/plugin-core/rules/required-modes.ts +0 -75
- package/src/lint/plugin-core/rules/required-type.ts +0 -28
- package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
- package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
- package/src/lint/plugin-core/rules/valid-border.ts +0 -57
- package/src/lint/plugin-core/rules/valid-color.ts +0 -265
- package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
- package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
- package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
- package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
- package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
- package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
- package/src/lint/plugin-core/rules/valid-link.ts +0 -41
- package/src/lint/plugin-core/rules/valid-number.ts +0 -63
- package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
- package/src/lint/plugin-core/rules/valid-string.ts +0 -41
- package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
- package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
- package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
- package/src/logger.ts +0 -213
- package/src/parse/index.ts +0 -124
- package/src/parse/load.ts +0 -172
- package/src/parse/normalize.ts +0 -163
- package/src/parse/process.ts +0 -251
- package/src/parse/token.ts +0 -553
- package/src/resolver/create-synthetic-resolver.ts +0 -86
- package/src/resolver/index.ts +0 -7
- package/src/resolver/load.ts +0 -215
- package/src/resolver/normalize.ts +0 -133
- package/src/resolver/validate.ts +0 -375
- package/src/types.ts +0 -468
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import { SHADOW_REQUIRED_PROPERTIES } from '@terrazzo/token-tools';
|
|
4
|
-
import type { LintRule } from '../../../types.js';
|
|
5
|
-
import { docsLink } from '../lib/docs.js';
|
|
6
|
-
|
|
7
|
-
export const VALID_SHADOW = 'core/valid-shadow';
|
|
8
|
-
|
|
9
|
-
const ERROR = 'ERROR';
|
|
10
|
-
const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP';
|
|
11
|
-
|
|
12
|
-
const rule: LintRule<typeof ERROR | typeof ERROR_INVALID_PROP> = {
|
|
13
|
-
meta: {
|
|
14
|
-
messages: {
|
|
15
|
-
[ERROR]: `Missing required properties: ${new Intl.ListFormat('en-us', { type: 'conjunction' }).format(SHADOW_REQUIRED_PROPERTIES)}.`,
|
|
16
|
-
[ERROR_INVALID_PROP]: 'Unknown property {{ key }}.',
|
|
17
|
-
},
|
|
18
|
-
docs: {
|
|
19
|
-
description: 'Require shadow tokens to follow the format.',
|
|
20
|
-
url: docsLink(VALID_SHADOW),
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
defaultOptions: {},
|
|
24
|
-
create({ tokens, report }) {
|
|
25
|
-
for (const t of Object.values(tokens)) {
|
|
26
|
-
if (t.aliasOf || !t.originalValue || t.$type !== 'shadow') {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
validateShadow(t.originalValue.$value, {
|
|
31
|
-
node: getObjMember(t.source.node, '$value') as momoa.ObjectNode,
|
|
32
|
-
filename: t.source.filename,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Note: we validate sub-properties using other checks like valid-dimension, valid-font-family, etc.
|
|
36
|
-
// The only thing remaining is to check that all properties exist (since missing properties won’t appear as invalid)
|
|
37
|
-
function validateShadow(
|
|
38
|
-
value: unknown,
|
|
39
|
-
{ node, filename }: { node: momoa.ObjectNode | momoa.ArrayNode; filename?: string },
|
|
40
|
-
) {
|
|
41
|
-
const wrappedValue = Array.isArray(value) ? value : [value];
|
|
42
|
-
for (let i = 0; i < wrappedValue.length; i++) {
|
|
43
|
-
if (
|
|
44
|
-
!wrappedValue[i] ||
|
|
45
|
-
typeof wrappedValue[i] !== 'object' ||
|
|
46
|
-
!SHADOW_REQUIRED_PROPERTIES.every((property) => property in wrappedValue[i])
|
|
47
|
-
) {
|
|
48
|
-
report({ messageId: ERROR, node, filename });
|
|
49
|
-
} else {
|
|
50
|
-
for (const key of Object.keys(wrappedValue[i])) {
|
|
51
|
-
if (![...SHADOW_REQUIRED_PROPERTIES, 'inset'].includes(key)) {
|
|
52
|
-
report({
|
|
53
|
-
messageId: ERROR_INVALID_PROP,
|
|
54
|
-
data: { key: JSON.stringify(key) },
|
|
55
|
-
node: getObjMember(node.type === 'Array' ? (node.elements[i]!.value as momoa.ObjectNode) : node, key),
|
|
56
|
-
filename,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export default rule;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import type { LintRule } from '../../../types.js';
|
|
4
|
-
import { docsLink } from '../lib/docs.js';
|
|
5
|
-
|
|
6
|
-
export const VALID_STRING = 'core/valid-string';
|
|
7
|
-
|
|
8
|
-
const ERROR = 'ERROR';
|
|
9
|
-
|
|
10
|
-
const rule: LintRule<typeof ERROR> = {
|
|
11
|
-
meta: {
|
|
12
|
-
messages: {
|
|
13
|
-
[ERROR]: 'Must be a string.',
|
|
14
|
-
},
|
|
15
|
-
docs: {
|
|
16
|
-
description: 'Require string tokens to follow the Terrazzo extension.',
|
|
17
|
-
url: docsLink(VALID_STRING),
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
defaultOptions: {},
|
|
21
|
-
create({ tokens, report }) {
|
|
22
|
-
for (const t of Object.values(tokens)) {
|
|
23
|
-
if (t.aliasOf || !t.originalValue || t.$type !== 'string') {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
validateString(t.originalValue.$value, {
|
|
28
|
-
node: getObjMember(t.source.node, '$value') as momoa.StringNode,
|
|
29
|
-
filename: t.source.filename,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
function validateString(value: unknown, { node, filename }: { node: momoa.StringNode; filename?: string }) {
|
|
33
|
-
if (typeof value !== 'string') {
|
|
34
|
-
report({ messageId: ERROR, node, filename });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export default rule;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import {
|
|
4
|
-
isAlias,
|
|
5
|
-
STROKE_STYLE_LINE_CAP_VALUES,
|
|
6
|
-
STROKE_STYLE_OBJECT_REQUIRED_PROPERTIES,
|
|
7
|
-
STROKE_STYLE_STRING_VALUES,
|
|
8
|
-
TRANSITION_REQUIRED_PROPERTIES,
|
|
9
|
-
} from '@terrazzo/token-tools';
|
|
10
|
-
import type { LintRule } from '../../../types.js';
|
|
11
|
-
import { docsLink } from '../lib/docs.js';
|
|
12
|
-
|
|
13
|
-
export const VALID_STROKE_STYLE = 'core/valid-stroke-style';
|
|
14
|
-
|
|
15
|
-
const ERROR_STR = 'ERROR_STR';
|
|
16
|
-
const ERROR_OBJ = 'ERROR_OBJ';
|
|
17
|
-
const ERROR_LINE_CAP = 'ERROR_LINE_CAP';
|
|
18
|
-
const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP';
|
|
19
|
-
|
|
20
|
-
const rule: LintRule<typeof ERROR_STR | typeof ERROR_OBJ | typeof ERROR_LINE_CAP | typeof ERROR_INVALID_PROP> = {
|
|
21
|
-
meta: {
|
|
22
|
-
messages: {
|
|
23
|
-
[ERROR_STR]: `Value most be one of ${new Intl.ListFormat('en-us', { type: 'disjunction' }).format(STROKE_STYLE_STRING_VALUES)}.`,
|
|
24
|
-
[ERROR_OBJ]: `Missing required properties: ${new Intl.ListFormat('en-us', { type: 'conjunction' }).format(TRANSITION_REQUIRED_PROPERTIES)}.`,
|
|
25
|
-
[ERROR_LINE_CAP]: `lineCap must be one of ${new Intl.ListFormat('en-us', { type: 'disjunction' }).format(STROKE_STYLE_LINE_CAP_VALUES)}.`,
|
|
26
|
-
[ERROR_INVALID_PROP]: 'Unknown property: {{ key }}.',
|
|
27
|
-
},
|
|
28
|
-
docs: {
|
|
29
|
-
description: 'Require strokeStyle tokens to follow the format.',
|
|
30
|
-
url: docsLink(VALID_STROKE_STYLE),
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
defaultOptions: {},
|
|
34
|
-
create({ tokens, report }) {
|
|
35
|
-
for (const t of Object.values(tokens)) {
|
|
36
|
-
if (t.aliasOf || !t.originalValue) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
switch (t.$type) {
|
|
41
|
-
case 'strokeStyle': {
|
|
42
|
-
validateStrokeStyle(t.originalValue.$value, {
|
|
43
|
-
node: getObjMember(t.source.node, '$value') as momoa.ObjectNode,
|
|
44
|
-
filename: t.source.filename,
|
|
45
|
-
});
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
case 'border': {
|
|
49
|
-
if (t.originalValue.$value && typeof t.originalValue.$value === 'object') {
|
|
50
|
-
const $valueNode = getObjMember(t.source.node, '$value') as momoa.ObjectNode;
|
|
51
|
-
if (t.originalValue.$value.style) {
|
|
52
|
-
validateStrokeStyle(t.originalValue.$value.style, {
|
|
53
|
-
node: getObjMember($valueNode, 'style') as momoa.ObjectNode,
|
|
54
|
-
filename: t.source.filename,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Note: we validate sub-properties using other checks like valid-dimension, valid-font-family, etc.
|
|
63
|
-
// The only thing remaining is to check that all properties exist (since missing properties won’t appear as invalid)
|
|
64
|
-
function validateStrokeStyle(
|
|
65
|
-
value: unknown,
|
|
66
|
-
{ node, filename }: { node: momoa.StringNode | momoa.ObjectNode; filename?: string },
|
|
67
|
-
) {
|
|
68
|
-
if (typeof value === 'string') {
|
|
69
|
-
if (
|
|
70
|
-
!isAlias(value) &&
|
|
71
|
-
!STROKE_STYLE_STRING_VALUES.includes(value as (typeof STROKE_STYLE_STRING_VALUES)[number])
|
|
72
|
-
) {
|
|
73
|
-
report({ messageId: ERROR_STR, node, filename });
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
} else if (value && typeof value === 'object') {
|
|
77
|
-
if (!STROKE_STYLE_OBJECT_REQUIRED_PROPERTIES.every((property) => property in value)) {
|
|
78
|
-
report({ messageId: ERROR_OBJ, node, filename });
|
|
79
|
-
}
|
|
80
|
-
if (!Array.isArray((value as any).dashArray)) {
|
|
81
|
-
report({ messageId: ERROR_OBJ, node: getObjMember(node as momoa.ObjectNode, 'dashArray'), filename });
|
|
82
|
-
}
|
|
83
|
-
if (!STROKE_STYLE_LINE_CAP_VALUES.includes((value as any).lineCap)) {
|
|
84
|
-
report({ messageId: ERROR_OBJ, node: getObjMember(node as momoa.ObjectNode, 'lineCap'), filename });
|
|
85
|
-
}
|
|
86
|
-
for (const key of Object.keys(value)) {
|
|
87
|
-
if (!['dashArray', 'lineCap'].includes(key)) {
|
|
88
|
-
report({
|
|
89
|
-
messageId: ERROR_INVALID_PROP,
|
|
90
|
-
data: { key: JSON.stringify(key) },
|
|
91
|
-
node: getObjMember(node as momoa.ObjectNode, key),
|
|
92
|
-
filename,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
report({ messageId: ERROR_OBJ, node, filename });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export default rule;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import { TRANSITION_REQUIRED_PROPERTIES } from '@terrazzo/token-tools';
|
|
4
|
-
import type { LintRule } from '../../../types.js';
|
|
5
|
-
import { docsLink } from '../lib/docs.js';
|
|
6
|
-
|
|
7
|
-
export const VALID_TRANSITION = 'core/valid-transition';
|
|
8
|
-
|
|
9
|
-
const ERROR = 'ERROR';
|
|
10
|
-
const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP';
|
|
11
|
-
|
|
12
|
-
const rule: LintRule<typeof ERROR | typeof ERROR_INVALID_PROP> = {
|
|
13
|
-
meta: {
|
|
14
|
-
messages: {
|
|
15
|
-
[ERROR]: `Missing required properties: ${new Intl.ListFormat('en-us', { type: 'conjunction' }).format(TRANSITION_REQUIRED_PROPERTIES)}.`,
|
|
16
|
-
[ERROR_INVALID_PROP]: 'Unknown property: {{ key }}.',
|
|
17
|
-
},
|
|
18
|
-
docs: {
|
|
19
|
-
description: 'Require transition tokens to follow the format.',
|
|
20
|
-
url: docsLink(VALID_TRANSITION),
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
defaultOptions: {},
|
|
24
|
-
create({ tokens, report }) {
|
|
25
|
-
for (const t of Object.values(tokens)) {
|
|
26
|
-
if (t.aliasOf || !t.originalValue || t.$type !== 'transition') {
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
validateTransition(t.originalValue.$value, {
|
|
31
|
-
node: getObjMember(t.source.node, '$value') as momoa.ObjectNode,
|
|
32
|
-
filename: t.source.filename,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Note: we validate sub-properties using other checks like valid-dimension, valid-font-family, etc.
|
|
37
|
-
// The only thing remaining is to check that all properties exist (since missing properties won’t appear as invalid)
|
|
38
|
-
function validateTransition(value: unknown, { node, filename }: { node: momoa.ObjectNode; filename?: string }) {
|
|
39
|
-
if (
|
|
40
|
-
!value ||
|
|
41
|
-
typeof value !== 'object' ||
|
|
42
|
-
!TRANSITION_REQUIRED_PROPERTIES.every((property) => property in value)
|
|
43
|
-
) {
|
|
44
|
-
report({ messageId: ERROR, node, filename });
|
|
45
|
-
} else {
|
|
46
|
-
for (const key of Object.keys(value)) {
|
|
47
|
-
if (!TRANSITION_REQUIRED_PROPERTIES.includes(key as (typeof TRANSITION_REQUIRED_PROPERTIES)[number])) {
|
|
48
|
-
report({
|
|
49
|
-
messageId: ERROR_INVALID_PROP,
|
|
50
|
-
data: { key: JSON.stringify(key) },
|
|
51
|
-
node: getObjMember(node, key),
|
|
52
|
-
filename,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export default rule;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { getObjMember } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import wcmatch from 'wildcard-match';
|
|
4
|
-
import type { LintRule } from '../../../types.js';
|
|
5
|
-
import { docsLink } from '../lib/docs.js';
|
|
6
|
-
|
|
7
|
-
export const VALID_TYPOGRAPHY = 'core/valid-typography';
|
|
8
|
-
|
|
9
|
-
const ERROR = 'ERROR';
|
|
10
|
-
const ERROR_MISSING = 'ERROR_MISSING';
|
|
11
|
-
|
|
12
|
-
export interface RuleRequiredTypographyPropertiesOptions {
|
|
13
|
-
/** Required typography properties */
|
|
14
|
-
requiredProperties: string[];
|
|
15
|
-
/** Token globs to ignore */
|
|
16
|
-
ignore?: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const rule: LintRule<typeof ERROR | typeof ERROR_MISSING, RuleRequiredTypographyPropertiesOptions> = {
|
|
20
|
-
meta: {
|
|
21
|
-
messages: {
|
|
22
|
-
[ERROR]: `Expected object, received {{ value }}.`,
|
|
23
|
-
[ERROR_MISSING]: `Missing required property "{{ property }}".`,
|
|
24
|
-
},
|
|
25
|
-
docs: {
|
|
26
|
-
description: 'Require typography tokens to follow the format.',
|
|
27
|
-
url: docsLink(VALID_TYPOGRAPHY),
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
defaultOptions: {
|
|
31
|
-
requiredProperties: ['fontFamily', 'fontSize', 'fontWeight', 'letterSpacing', 'lineHeight'],
|
|
32
|
-
},
|
|
33
|
-
create({ tokens, options, report }) {
|
|
34
|
-
const isIgnored = options.ignore ? wcmatch(options.ignore) : () => false;
|
|
35
|
-
for (const t of Object.values(tokens)) {
|
|
36
|
-
if (t.aliasOf || !t.originalValue || t.$type !== 'typography' || isIgnored(t.id)) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
validateTypography(t.originalValue.$value, {
|
|
41
|
-
node: getObjMember(t.source.node, '$value') as momoa.ObjectNode,
|
|
42
|
-
filename: t.source.filename,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Note: we validate sub-properties using other checks like valid-dimension, valid-font-family, etc.
|
|
46
|
-
// The only thing remaining is to check that all properties exist (since missing properties won’t appear as invalid)
|
|
47
|
-
function validateTypography(value: unknown, { node, filename }: { node: momoa.ObjectNode; filename?: string }) {
|
|
48
|
-
if (value && typeof value === 'object') {
|
|
49
|
-
for (const property of options.requiredProperties) {
|
|
50
|
-
if (!(property in value)) {
|
|
51
|
-
report({ messageId: ERROR_MISSING, data: { property }, node, filename });
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
report({
|
|
56
|
-
messageId: ERROR,
|
|
57
|
-
data: { value: JSON.stringify(value) },
|
|
58
|
-
node,
|
|
59
|
-
filename,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export default rule;
|
package/src/logger.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import wcmatch from 'wildcard-match';
|
|
4
|
-
import { codeFrameColumns } from './lib/code-frame.js';
|
|
5
|
-
|
|
6
|
-
export const LOG_ORDER = ['error', 'warn', 'info', 'debug'] as const;
|
|
7
|
-
export type LogSeverity = 'error' | 'warn' | 'info' | 'debug';
|
|
8
|
-
export type LogLevel = LogSeverity | 'silent';
|
|
9
|
-
export type LogGroup = 'config' | 'parser' | 'lint' | 'plugin' | 'server';
|
|
10
|
-
|
|
11
|
-
export interface LogEntry {
|
|
12
|
-
/** Originator of log message */
|
|
13
|
-
group: LogGroup;
|
|
14
|
-
/** Error message to be logged */
|
|
15
|
-
message: string;
|
|
16
|
-
/** Prefix message with label */
|
|
17
|
-
label?: string;
|
|
18
|
-
/** File in disk */
|
|
19
|
-
filename?: URL;
|
|
20
|
-
/**
|
|
21
|
-
* Continue on error?
|
|
22
|
-
* @default false
|
|
23
|
-
*/
|
|
24
|
-
continueOnError?: boolean;
|
|
25
|
-
/** Show a code frame for the erring node */
|
|
26
|
-
node?: momoa.AnyNode;
|
|
27
|
-
/** To show a code frame, provide the original source code */
|
|
28
|
-
src?: string;
|
|
29
|
-
/** Display performance timing */
|
|
30
|
-
timing?: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DebugEntry {
|
|
34
|
-
group: LogGroup;
|
|
35
|
-
/** Error message to be logged */
|
|
36
|
-
message: string;
|
|
37
|
-
/** Current subtask or submodule */
|
|
38
|
-
label?: string;
|
|
39
|
-
/** Show code below message */
|
|
40
|
-
codeFrame?: { src: string; line: number; column: number };
|
|
41
|
-
/** Display performance timing */
|
|
42
|
-
timing?: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const MESSAGE_COLOR: Record<string, typeof pc.red | undefined> = { error: pc.red, warn: pc.yellow };
|
|
46
|
-
|
|
47
|
-
const timeFormatter = new Intl.DateTimeFormat('en-us', {
|
|
48
|
-
hour: 'numeric',
|
|
49
|
-
hour12: false,
|
|
50
|
-
minute: 'numeric',
|
|
51
|
-
second: 'numeric',
|
|
52
|
-
fractionalSecondDigits: 3,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {Entry} entry
|
|
57
|
-
* @param {Severity} severity
|
|
58
|
-
* @return {string}
|
|
59
|
-
*/
|
|
60
|
-
export function formatMessage(entry: LogEntry, severity: LogSeverity) {
|
|
61
|
-
let message = entry.message;
|
|
62
|
-
message = `[${entry.group}${entry.label ? `:${entry.label}` : ''}] ${message}`;
|
|
63
|
-
if (severity in MESSAGE_COLOR) {
|
|
64
|
-
message = MESSAGE_COLOR[severity]!(message);
|
|
65
|
-
}
|
|
66
|
-
if (typeof entry.timing === 'number') {
|
|
67
|
-
message = `${message} ${formatTiming(entry.timing)}`;
|
|
68
|
-
}
|
|
69
|
-
if (entry.node) {
|
|
70
|
-
const start = entry.node?.loc?.start ?? { line: 0, column: 0 };
|
|
71
|
-
// strip "file://" protocol, but not href
|
|
72
|
-
const loc = entry.filename
|
|
73
|
-
? `${entry.filename?.href.replace(/^file:\/\//, '')}:${start?.line ?? 0}:${start?.column ?? 0}\n\n`
|
|
74
|
-
: '';
|
|
75
|
-
const codeFrame = codeFrameColumns(
|
|
76
|
-
entry.src ?? momoa.print(entry.node, { indent: 2 }),
|
|
77
|
-
{ start },
|
|
78
|
-
{ highlightCode: false },
|
|
79
|
-
);
|
|
80
|
-
message = `${message}\n\n${loc}${codeFrame}`;
|
|
81
|
-
}
|
|
82
|
-
return message;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export default class Logger {
|
|
86
|
-
level = 'info' as LogLevel;
|
|
87
|
-
debugScope = '*';
|
|
88
|
-
errorCount = 0;
|
|
89
|
-
warnCount = 0;
|
|
90
|
-
infoCount = 0;
|
|
91
|
-
debugCount = 0;
|
|
92
|
-
|
|
93
|
-
constructor(options?: { level?: LogLevel; debugScope?: string }) {
|
|
94
|
-
if (options?.level) {
|
|
95
|
-
this.level = options.level;
|
|
96
|
-
}
|
|
97
|
-
if (options?.debugScope) {
|
|
98
|
-
this.debugScope = options.debugScope;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
setLevel(level: LogLevel) {
|
|
103
|
-
this.level = level;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Log an error message (always; can’t be silenced) */
|
|
107
|
-
error(...entries: LogEntry[]) {
|
|
108
|
-
const message: string[] = [];
|
|
109
|
-
let firstNode: momoa.AnyNode | undefined;
|
|
110
|
-
for (const entry of entries) {
|
|
111
|
-
this.errorCount++;
|
|
112
|
-
message.push(formatMessage(entry, 'error'));
|
|
113
|
-
if (entry.node) {
|
|
114
|
-
firstNode = entry.node;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (entries.every((e) => e.continueOnError)) {
|
|
118
|
-
// biome-ignore lint/suspicious/noConsole: this is a logger
|
|
119
|
-
console.error(message.join('\n\n'));
|
|
120
|
-
} else {
|
|
121
|
-
const e = firstNode ? new TokensJSONError(message.join('\n\n')) : new Error(message.join('\n\n'));
|
|
122
|
-
throw e;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Log an info message (if logging level permits) */
|
|
127
|
-
info(...entries: LogEntry[]) {
|
|
128
|
-
for (const entry of entries) {
|
|
129
|
-
this.infoCount++;
|
|
130
|
-
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('info')) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const message = formatMessage(entry, 'info');
|
|
134
|
-
// biome-ignore lint/suspicious/noConsole: this is a logger
|
|
135
|
-
console.log(message);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Log a warning message (if logging level permits) */
|
|
140
|
-
warn(...entries: LogEntry[]) {
|
|
141
|
-
for (const entry of entries) {
|
|
142
|
-
this.warnCount++;
|
|
143
|
-
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('warn')) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
const message = formatMessage(entry, 'warn');
|
|
147
|
-
|
|
148
|
-
// biome-ignore lint/suspicious/noConsole: this is a logger
|
|
149
|
-
console.warn(message);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Log a diagnostics message (if logging level permits) */
|
|
154
|
-
debug(...entries: DebugEntry[]) {
|
|
155
|
-
for (const entry of entries) {
|
|
156
|
-
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('debug')) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
this.debugCount++;
|
|
160
|
-
|
|
161
|
-
let message = formatMessage(entry, 'debug');
|
|
162
|
-
|
|
163
|
-
const debugPrefix = entry.label ? `${entry.group}:${entry.label}` : entry.group;
|
|
164
|
-
if (this.debugScope !== '*' && !wcmatch(this.debugScope)(debugPrefix)) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// debug color
|
|
169
|
-
message
|
|
170
|
-
.replace(/\[config[^\]]+\]/, (match) => pc.green(match))
|
|
171
|
-
.replace(/\[parser[^\]]+\]/, (match) => pc.magenta(match))
|
|
172
|
-
.replace(/\[lint[^\]]+\]/, (match) => pc.yellow(match))
|
|
173
|
-
.replace(/\[plugin[^\]]+\]/, (match) => pc.cyan(match));
|
|
174
|
-
|
|
175
|
-
message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
|
|
176
|
-
if (typeof entry.timing === 'number') {
|
|
177
|
-
message = `${message} ${formatTiming(entry.timing)}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// biome-ignore lint/suspicious/noConsole: this is a logger
|
|
181
|
-
console.log(message);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/** Get stats for current logger instance */
|
|
186
|
-
stats() {
|
|
187
|
-
return {
|
|
188
|
-
errorCount: this.errorCount,
|
|
189
|
-
warnCount: this.warnCount,
|
|
190
|
-
infoCount: this.infoCount,
|
|
191
|
-
debugCount: this.debugCount,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function formatTiming(timing: number): string {
|
|
197
|
-
let output = '';
|
|
198
|
-
if (timing < 1_000) {
|
|
199
|
-
output = `${Math.round(timing * 100) / 100}ms`;
|
|
200
|
-
} else if (timing < 60_000) {
|
|
201
|
-
output = `${Math.round(timing) / 1_000}s`;
|
|
202
|
-
} else {
|
|
203
|
-
output = `${Math.round(timing / 1_000) / 60}m`;
|
|
204
|
-
}
|
|
205
|
-
return pc.dim(`[${output}]`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export class TokensJSONError extends Error {
|
|
209
|
-
constructor(message: string) {
|
|
210
|
-
super(message);
|
|
211
|
-
this.name = 'TokensJSONError';
|
|
212
|
-
}
|
|
213
|
-
}
|
package/src/parse/index.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type fsType from 'node:fs/promises';
|
|
2
|
-
import type { InputSource, InputSourceWithDocument } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import { pluralize, type TokenNormalizedSet } from '@terrazzo/token-tools';
|
|
4
|
-
import lintRunner from '../lint/index.js';
|
|
5
|
-
import Logger from '../logger.js';
|
|
6
|
-
import { createSyntheticResolver } from '../resolver/create-synthetic-resolver.js';
|
|
7
|
-
import { loadResolver } from '../resolver/load.js';
|
|
8
|
-
import type { ConfigInit, ParseOptions, Resolver } from '../types.js';
|
|
9
|
-
import { loadSources } from './load.js';
|
|
10
|
-
|
|
11
|
-
export interface ParseResult {
|
|
12
|
-
tokens: TokenNormalizedSet;
|
|
13
|
-
sources: InputSourceWithDocument[];
|
|
14
|
-
resolver: Resolver;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Parse */
|
|
18
|
-
export default async function parse(
|
|
19
|
-
_input: InputSource | InputSource[],
|
|
20
|
-
{
|
|
21
|
-
logger = new Logger(),
|
|
22
|
-
req = defaultReq,
|
|
23
|
-
skipLint = false,
|
|
24
|
-
config = {} as ConfigInit,
|
|
25
|
-
continueOnError = false,
|
|
26
|
-
yamlToMomoa,
|
|
27
|
-
transform,
|
|
28
|
-
}: ParseOptions = {} as ParseOptions,
|
|
29
|
-
): Promise<ParseResult> {
|
|
30
|
-
const inputs = Array.isArray(_input) ? _input : [_input];
|
|
31
|
-
let tokens: TokenNormalizedSet = {};
|
|
32
|
-
let resolver: Resolver | undefined;
|
|
33
|
-
let sources: InputSourceWithDocument[] = [];
|
|
34
|
-
|
|
35
|
-
const totalStart = performance.now();
|
|
36
|
-
|
|
37
|
-
// 1. Load tokens
|
|
38
|
-
const initStart = performance.now();
|
|
39
|
-
const resolverResult = await loadResolver(inputs, { config, logger, req, yamlToMomoa });
|
|
40
|
-
// 1a. Resolver
|
|
41
|
-
if (resolverResult.resolver) {
|
|
42
|
-
tokens = resolverResult.tokens;
|
|
43
|
-
sources = resolverResult.sources;
|
|
44
|
-
resolver = resolverResult.resolver;
|
|
45
|
-
} else {
|
|
46
|
-
// 1b. No resolver
|
|
47
|
-
const tokenResult = await loadSources(inputs, {
|
|
48
|
-
req,
|
|
49
|
-
logger,
|
|
50
|
-
config,
|
|
51
|
-
continueOnError,
|
|
52
|
-
yamlToMomoa,
|
|
53
|
-
transform,
|
|
54
|
-
});
|
|
55
|
-
tokens = tokenResult.tokens;
|
|
56
|
-
sources = tokenResult.sources;
|
|
57
|
-
}
|
|
58
|
-
logger.debug({
|
|
59
|
-
message: 'Loaded tokens',
|
|
60
|
-
group: 'parser',
|
|
61
|
-
label: 'core',
|
|
62
|
-
timing: performance.now() - initStart,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
if (skipLint !== true && config?.plugins?.length) {
|
|
66
|
-
const lintStart = performance.now();
|
|
67
|
-
await lintRunner({ tokens, sources, config, logger });
|
|
68
|
-
logger.debug({
|
|
69
|
-
message: 'Lint finished',
|
|
70
|
-
group: 'plugin',
|
|
71
|
-
label: 'lint',
|
|
72
|
-
timing: performance.now() - lintStart,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const resolverTiming = performance.now();
|
|
77
|
-
const finalResolver = resolver || (await createSyntheticResolver(tokens, { config, logger, req, sources }));
|
|
78
|
-
logger.debug({
|
|
79
|
-
message: 'Resolver finalized',
|
|
80
|
-
group: 'parser',
|
|
81
|
-
label: 'core',
|
|
82
|
-
timing: performance.now() - resolverTiming,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
logger.debug({
|
|
86
|
-
message: 'Finish all parser tasks',
|
|
87
|
-
group: 'parser',
|
|
88
|
-
label: 'core',
|
|
89
|
-
timing: performance.now() - totalStart,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (continueOnError) {
|
|
93
|
-
const { errorCount } = logger.stats();
|
|
94
|
-
if (errorCount > 0) {
|
|
95
|
-
logger.error({
|
|
96
|
-
group: 'parser',
|
|
97
|
-
message: `Parser encountered ${errorCount} ${pluralize(errorCount, 'error', 'errors')}. Exiting.`,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
tokens,
|
|
104
|
-
sources,
|
|
105
|
-
resolver: finalResolver,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let fs: typeof fsType | undefined;
|
|
110
|
-
|
|
111
|
-
/** Fallback req */
|
|
112
|
-
async function defaultReq(src: URL, _origin: URL) {
|
|
113
|
-
if (src.protocol === 'file:') {
|
|
114
|
-
if (!fs) {
|
|
115
|
-
fs = await import('node:fs/promises');
|
|
116
|
-
}
|
|
117
|
-
return await fs.readFile(src, 'utf8');
|
|
118
|
-
}
|
|
119
|
-
const res = await fetch(src);
|
|
120
|
-
if (!res.ok) {
|
|
121
|
-
throw new Error(`${src} responded with ${res.status}\n${await res.text()}`);
|
|
122
|
-
}
|
|
123
|
-
return await res.text();
|
|
124
|
-
}
|