@terrazzo/parser 0.3.3 → 0.3.4
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/CHANGELOG.md +9 -0
- package/dist/build/index.js +3 -1
- package/dist/build/index.js.map +1 -1
- package/dist/parse/alias.js +8 -0
- package/dist/parse/alias.js.map +1 -1
- package/dist/parse/index.d.ts +6 -3
- package/dist/parse/index.js +54 -204
- package/dist/parse/index.js.map +1 -1
- package/dist/parse/json.d.ts +20 -4
- package/dist/parse/json.js +79 -4
- package/dist/parse/json.js.map +1 -1
- package/dist/parse/validate.d.ts +13 -1
- package/dist/parse/validate.js +113 -4
- package/dist/parse/validate.js.map +1 -1
- package/package.json +7 -7
- package/src/build/index.ts +4 -1
- package/src/parse/alias.ts +17 -8
- package/src/parse/index.ts +80 -239
- package/src/parse/json.ts +108 -7
- package/src/parse/validate.ts +144 -3
package/src/parse/validate.ts
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
type ObjectNode,
|
|
5
5
|
type StringNode,
|
|
6
6
|
type ValueNode,
|
|
7
|
+
evaluate,
|
|
7
8
|
print,
|
|
8
9
|
} from '@humanwhocodes/momoa';
|
|
9
|
-
import { isAlias } from '@terrazzo/token-tools';
|
|
10
|
+
import { type Token, type TokenNormalized, isAlias, isTokenMatch, splitID } from '@terrazzo/token-tools';
|
|
10
11
|
import type Logger from '../logger.js';
|
|
11
|
-
import {
|
|
12
|
+
import type { ConfigInit } from '../types.js';
|
|
13
|
+
import { getObjMembers, injectObjMembers } from './json.js';
|
|
12
14
|
|
|
13
15
|
const listFormat = new Intl.ListFormat('en-us', { type: 'disjunction' });
|
|
14
16
|
|
|
@@ -510,7 +512,7 @@ export function validateTransition($value: ValueNode, node: AnyNode, { filename,
|
|
|
510
512
|
* object) to see if it’s a valid DTCG token or not. Keeping the parent key
|
|
511
513
|
* really helps in debug messages.
|
|
512
514
|
*/
|
|
513
|
-
export
|
|
515
|
+
export function validateTokenMemberNode(node: MemberNode, { filename, src, logger }: ValidateOptions) {
|
|
514
516
|
if (node.type !== 'Member' && node.type !== 'Object') {
|
|
515
517
|
logger.error({
|
|
516
518
|
message: `Expected Object, received ${JSON.stringify(
|
|
@@ -658,3 +660,142 @@ export default function validate(node: MemberNode, { filename, src, logger }: Va
|
|
|
658
660
|
}
|
|
659
661
|
}
|
|
660
662
|
}
|
|
663
|
+
|
|
664
|
+
export interface ValidateTokenNodeOptions {
|
|
665
|
+
subpath: string[];
|
|
666
|
+
src: string;
|
|
667
|
+
filename: URL;
|
|
668
|
+
config: ConfigInit;
|
|
669
|
+
logger: Logger;
|
|
670
|
+
parent?: AnyNode;
|
|
671
|
+
$typeInheritance?: Record<string, Token['$type']>;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export default function validateTokenNode(
|
|
675
|
+
node: MemberNode,
|
|
676
|
+
{ config, filename, logger, parent, src, subpath, $typeInheritance }: ValidateTokenNodeOptions,
|
|
677
|
+
): TokenNormalized | undefined {
|
|
678
|
+
// don’t validate $value
|
|
679
|
+
if (subpath.includes('$value') || node.value.type !== 'Object') {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const members = getObjMembers(node.value);
|
|
684
|
+
|
|
685
|
+
// keep track of $types
|
|
686
|
+
if ($typeInheritance && members.$type && members.$type.type === 'String' && !members.$value) {
|
|
687
|
+
// @ts-ignore
|
|
688
|
+
$typeInheritance[subpath.join('.') || '.'] = node.value.members.find((m) => m.name.value === '$type');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// don’t validate $extensions or $defs
|
|
692
|
+
if (!members.$value || subpath.includes('$extensions') || subpath.includes('$deps')) {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const id = subpath.join('.');
|
|
697
|
+
|
|
698
|
+
if (!subpath.includes('.$value') && members.value) {
|
|
699
|
+
logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, filename, node, src });
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const extensions = members.$extensions ? getObjMembers(members.$extensions as ObjectNode) : undefined;
|
|
703
|
+
const sourceNode = structuredClone(node);
|
|
704
|
+
|
|
705
|
+
// get parent type by taking the closest-scoped $type (length === closer)
|
|
706
|
+
let parent$type: Token['$type'] | undefined;
|
|
707
|
+
let longestPath = '';
|
|
708
|
+
for (const [k, v] of Object.entries($typeInheritance ?? {})) {
|
|
709
|
+
if (k === '.' || id.startsWith(k)) {
|
|
710
|
+
if (k.length > longestPath.length) {
|
|
711
|
+
parent$type = v;
|
|
712
|
+
longestPath = k;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (parent$type && !members.$type) {
|
|
717
|
+
sourceNode.value = injectObjMembers(
|
|
718
|
+
// @ts-ignore
|
|
719
|
+
sourceNode.value,
|
|
720
|
+
[parent$type],
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
validateTokenMemberNode(sourceNode, { filename, src, logger });
|
|
725
|
+
|
|
726
|
+
// All tokens must be valid, so we want to validate it up till this
|
|
727
|
+
// point. However, if we are ignoring this token (or respecting
|
|
728
|
+
// $deprecated, we can omit it from the output.
|
|
729
|
+
const $deprecated = members.$deprecated && (evaluate(members.$deprecated) as string | boolean | undefined);
|
|
730
|
+
if ((config.ignore.deprecated && $deprecated) || (config.ignore.tokens && isTokenMatch(id, config.ignore.tokens))) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const group: TokenNormalized['group'] = { id: splitID(id).group!, tokens: [] };
|
|
735
|
+
if (parent$type) {
|
|
736
|
+
group.$type =
|
|
737
|
+
// @ts-ignore
|
|
738
|
+
parent$type.value.value;
|
|
739
|
+
}
|
|
740
|
+
// note: this will also include sibling tokens, so be selective about only accessing group-specific properties
|
|
741
|
+
const groupMembers = getObjMembers(
|
|
742
|
+
// @ts-ignore
|
|
743
|
+
parent,
|
|
744
|
+
);
|
|
745
|
+
if (groupMembers.$description) {
|
|
746
|
+
group.$description = evaluate(groupMembers.$description) as string;
|
|
747
|
+
}
|
|
748
|
+
if (groupMembers.$extensions) {
|
|
749
|
+
group.$extensions = evaluate(groupMembers.$extensions) as Record<string, unknown>;
|
|
750
|
+
}
|
|
751
|
+
const token: TokenNormalized = {
|
|
752
|
+
// @ts-ignore
|
|
753
|
+
$type: members.$type?.value ?? parent$type?.value.value,
|
|
754
|
+
// @ts-ignore
|
|
755
|
+
$value: evaluate(members.$value),
|
|
756
|
+
id,
|
|
757
|
+
// @ts-ignore
|
|
758
|
+
mode: {},
|
|
759
|
+
// @ts-ignore
|
|
760
|
+
originalValue: evaluate(node.value),
|
|
761
|
+
group,
|
|
762
|
+
source: {
|
|
763
|
+
loc: filename ? filename.href : undefined,
|
|
764
|
+
// @ts-ignore
|
|
765
|
+
node: sourceNode.value,
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
// @ts-ignore
|
|
769
|
+
if (members.$description?.value) {
|
|
770
|
+
// @ts-ignore
|
|
771
|
+
token.$description = members.$description.value;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// handle modes
|
|
775
|
+
// note that circular refs are avoided here, such as not duplicating `modes`
|
|
776
|
+
const modeValues = extensions?.mode
|
|
777
|
+
? getObjMembers(
|
|
778
|
+
// @ts-ignore
|
|
779
|
+
extensions.mode,
|
|
780
|
+
)
|
|
781
|
+
: {};
|
|
782
|
+
for (const mode of ['.', ...Object.keys(modeValues)]) {
|
|
783
|
+
token.mode[mode] = {
|
|
784
|
+
id: token.id,
|
|
785
|
+
// @ts-ignore
|
|
786
|
+
$type: token.$type,
|
|
787
|
+
// @ts-ignore
|
|
788
|
+
$value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
|
|
789
|
+
source: {
|
|
790
|
+
loc: filename ? filename.href : undefined,
|
|
791
|
+
// @ts-ignore
|
|
792
|
+
node: mode === '.' ? structuredClone(token.source.node) : modeValues[mode],
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
if (token.$description) {
|
|
796
|
+
token.mode[mode]!.$description = token.$description;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return token;
|
|
801
|
+
}
|