@markuplint/ml-spec 2.0.0-rc.0 → 2.0.0-rc.1
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/lib/attributes.d.ts +77 -11
- package/lib/get-attr-specs.d.ts +7 -0
- package/lib/get-attr-specs.js +95 -0
- package/lib/get-spec-by-tag-name.js +11 -3
- package/lib/get-spec.js +22 -5
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/permitted-structres.d.ts +12 -0
- package/lib/types.d.ts +16 -13
- package/package.json +6 -4
- package/schemas/attributes.schema.json +168 -181
- package/schemas/global-attributes.schema.json +735 -6
- package/schemas/permitted-structures.schema.json +19 -7
- package/src/attributes.ts +1386 -108
- package/src/get-attr-specs.spec.ts +44 -0
- package/src/get-attr-specs.ts +111 -0
- package/src/get-spec-by-tag-name.ts +14 -6
- package/src/get-spec.spec.ts +10 -17
- package/src/get-spec.ts +27 -6
- package/src/index.ts +1 -0
- package/src/permitted-structres.ts +12 -0
- package/src/types.ts +20 -13
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import htmlSpec from '@markuplint/html-spec';
|
|
2
|
+
|
|
3
|
+
import { getAttrSpecs } from './get-attr-specs';
|
|
4
|
+
|
|
5
|
+
describe('getSpec', () => {
|
|
6
|
+
test('svg:image[x]', () => {
|
|
7
|
+
const specs = getAttrSpecs('svg:image', htmlSpec);
|
|
8
|
+
const x = specs?.find(spec => spec.name === 'x');
|
|
9
|
+
expect(x).toStrictEqual({
|
|
10
|
+
ref: 'https://svgwg.org/svg2-draft/geometry.html#XProperty',
|
|
11
|
+
defaultValue: '0',
|
|
12
|
+
name: 'x',
|
|
13
|
+
type: ['<svg-length>', '<percentage>'],
|
|
14
|
+
animatable: true,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('svg:foreignObject[x]', () => {
|
|
19
|
+
const specs = getAttrSpecs('svg:foreignObject', htmlSpec);
|
|
20
|
+
const x = specs?.find(spec => spec.name === 'x');
|
|
21
|
+
expect(x).toStrictEqual({
|
|
22
|
+
ref: 'https://svgwg.org/svg2-draft/geometry.html#XProperty',
|
|
23
|
+
defaultValue: '0',
|
|
24
|
+
description:
|
|
25
|
+
'The x coordinate of the foreignObject. Value type: <length>|<percentage> ; Default value: 0; Animatable: yes',
|
|
26
|
+
name: 'x',
|
|
27
|
+
type: ['<svg-length>', '<percentage>'],
|
|
28
|
+
animatable: true,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('svg:linearGradient[xlink:href]', () => {
|
|
33
|
+
const specs = getAttrSpecs('svg:linearGradient', htmlSpec);
|
|
34
|
+
const x = specs?.find(spec => spec.name === 'xlink:href');
|
|
35
|
+
expect(x).toStrictEqual({
|
|
36
|
+
ref: 'https://www.w3.org/TR/xlink11/#link-locators',
|
|
37
|
+
description:
|
|
38
|
+
'Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.An <IRI> reference to another <linearGradient> element that will be used as a template. Value type: <IRI> ; Default value: none; Animatable: yes',
|
|
39
|
+
name: 'xlink:href',
|
|
40
|
+
type: 'URL',
|
|
41
|
+
deprecated: true,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { MLMLSpec, Attribute } from './types';
|
|
2
|
+
|
|
3
|
+
const cacheMap = new Map<string, Attribute[] | null>();
|
|
4
|
+
const schemaCache = new WeakSet<MLMLSpec>();
|
|
5
|
+
|
|
6
|
+
export function getAttrSpecs(nameWithNS: string, schema: MLMLSpec) {
|
|
7
|
+
if (!schemaCache.has(schema)) {
|
|
8
|
+
cacheMap.clear();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const cache = cacheMap.get(nameWithNS);
|
|
12
|
+
|
|
13
|
+
if (cache !== undefined) {
|
|
14
|
+
return cache;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
schemaCache.add(schema);
|
|
18
|
+
|
|
19
|
+
const elSpec = schema.specs.find(spec => spec.name === nameWithNS);
|
|
20
|
+
if (!elSpec) {
|
|
21
|
+
cacheMap.set(nameWithNS, null);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const globalAttrs = schema.def['#globalAttrs'];
|
|
26
|
+
let attrs: Record<string, Partial<Attribute>> = {};
|
|
27
|
+
|
|
28
|
+
for (const catName in elSpec.globalAttrs) {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
const catAttrs: boolean | string[] = elSpec.globalAttrs[catName];
|
|
31
|
+
if (!catAttrs) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (typeof catAttrs === 'boolean') {
|
|
35
|
+
const global = globalAttrs[catName];
|
|
36
|
+
attrs = {
|
|
37
|
+
...attrs,
|
|
38
|
+
...global,
|
|
39
|
+
};
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(catAttrs)) {
|
|
43
|
+
const global = globalAttrs[catName];
|
|
44
|
+
if (!global) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
catAttrs.forEach(selectedName => {
|
|
48
|
+
const selected = global[selectedName];
|
|
49
|
+
attrs[selectedName] = {
|
|
50
|
+
...attrs[selectedName],
|
|
51
|
+
...selected,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const attrName in elSpec.attributes) {
|
|
59
|
+
const attr = elSpec.attributes[attrName];
|
|
60
|
+
if (!attr) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const current = attrs[attrName] as Omit<Attribute, 'name'> | undefined;
|
|
65
|
+
|
|
66
|
+
attrs[attrName] = {
|
|
67
|
+
description: '',
|
|
68
|
+
...current,
|
|
69
|
+
...attr,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const attrList: Attribute[] = Object.keys(attrs).map<Attribute>(name => {
|
|
74
|
+
const attr = attrs[name];
|
|
75
|
+
return { name, type: 'Any', ...attr };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for (const attr of attrList) {
|
|
79
|
+
if (!attr.type) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`The type is empty in the ${attr.name} attribute of the ${nameWithNS} element,
|
|
82
|
+
'packages',
|
|
83
|
+
'@markuplint',
|
|
84
|
+
'html-spec',
|
|
85
|
+
'src',
|
|
86
|
+
'attributes',
|
|
87
|
+
nameWithNS.replace(':', '_') + '.json',
|
|
88
|
+
)})`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
attrList.sort(nameCompare);
|
|
94
|
+
|
|
95
|
+
cacheMap.set(nameWithNS, attrList);
|
|
96
|
+
return attrList;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type HasName = { name: string };
|
|
100
|
+
|
|
101
|
+
export function nameCompare(a: HasName | string, b: HasName | string) {
|
|
102
|
+
const nameA = typeof a === 'string' ? a : a.name.toUpperCase();
|
|
103
|
+
const nameB = typeof b === 'string' ? b : b.name.toUpperCase();
|
|
104
|
+
if (nameA < nameB) {
|
|
105
|
+
return -1;
|
|
106
|
+
}
|
|
107
|
+
if (nameA > nameB) {
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MLDOMElementSpec, MLMLSpec, SpecOM } from './types';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { getAttrSpecs } from './get-attr-specs';
|
|
4
|
+
|
|
5
|
+
const cacheMap = new WeakMap<MLMLSpec, SpecOM>();
|
|
6
|
+
|
|
7
|
+
function getSpecOM(spec: MLMLSpec): SpecOM {
|
|
8
|
+
const cache = cacheMap.get(spec);
|
|
9
|
+
if (cache) {
|
|
10
|
+
return cache;
|
|
11
|
+
}
|
|
4
12
|
const som: SpecOM = {};
|
|
5
|
-
for (const el of specs) {
|
|
13
|
+
for (const el of spec.specs) {
|
|
14
|
+
const attributes = getAttrSpecs(el.name, spec);
|
|
6
15
|
som[el.name] = {
|
|
7
16
|
experimental: !!el.experimental,
|
|
8
17
|
obsolete: typeof el.obsolete === 'boolean' ? !!el.obsolete : el.obsolete ? el.obsolete.alt : false,
|
|
@@ -10,11 +19,10 @@ function getSpecOM({ specs }: MLMLSpec): SpecOM {
|
|
|
10
19
|
nonStandard: !!el.nonStandard,
|
|
11
20
|
categories: el.categories,
|
|
12
21
|
permittedStructures: el.permittedStructures,
|
|
13
|
-
attributes:
|
|
14
|
-
(attr: Attribute | string): attr is Attribute => !(typeof attr === 'string'),
|
|
15
|
-
),
|
|
22
|
+
attributes: Object.values(attributes || {}),
|
|
16
23
|
};
|
|
17
24
|
}
|
|
25
|
+
cacheMap.set(spec, som);
|
|
18
26
|
return som;
|
|
19
27
|
}
|
|
20
28
|
|
package/src/get-spec.spec.ts
CHANGED
|
@@ -1,28 +1,26 @@
|
|
|
1
|
-
import type { Attribute } from '@markuplint/html-spec';
|
|
2
|
-
|
|
3
1
|
import htmlSpec from '@markuplint/html-spec';
|
|
4
2
|
|
|
5
3
|
import { getSpec } from './get-spec';
|
|
6
4
|
|
|
7
5
|
describe('getSpec', () => {
|
|
8
|
-
test('', () => {
|
|
9
|
-
const exAttr
|
|
10
|
-
|
|
6
|
+
test('Overriding', () => {
|
|
7
|
+
const exAttr = {
|
|
8
|
+
ref: 'N/A',
|
|
11
9
|
type: 'Boolean',
|
|
12
10
|
description: 'For the unit test.',
|
|
13
|
-
};
|
|
14
|
-
const exAttr2
|
|
15
|
-
|
|
11
|
+
} as const;
|
|
12
|
+
const exAttr2 = {
|
|
13
|
+
ref: 'N/A',
|
|
16
14
|
type: 'Boolean',
|
|
17
15
|
description: 'For the unit test. Override.',
|
|
18
|
-
};
|
|
16
|
+
} as const;
|
|
19
17
|
const mergedSpec = getSpec([
|
|
20
18
|
htmlSpec,
|
|
21
19
|
{
|
|
22
20
|
specs: [
|
|
23
21
|
{
|
|
24
22
|
name: 'a',
|
|
25
|
-
attributes:
|
|
23
|
+
attributes: { 'extended-attr': exAttr },
|
|
26
24
|
},
|
|
27
25
|
],
|
|
28
26
|
},
|
|
@@ -30,17 +28,12 @@ describe('getSpec', () => {
|
|
|
30
28
|
specs: [
|
|
31
29
|
{
|
|
32
30
|
name: 'a',
|
|
33
|
-
attributes:
|
|
31
|
+
attributes: { 'extended-attr': exAttr2 },
|
|
34
32
|
},
|
|
35
33
|
],
|
|
36
34
|
},
|
|
37
35
|
]);
|
|
38
36
|
const aElAttrs = mergedSpec.specs.find(el => el.name === 'a')!.attributes;
|
|
39
|
-
expect(
|
|
40
|
-
aElAttrs.find((attr): attr is Attribute => !(typeof attr === 'string') && attr.name === 'href')!.name,
|
|
41
|
-
).toBe('href');
|
|
42
|
-
expect(
|
|
43
|
-
aElAttrs.find((attr): attr is Attribute => !(typeof attr === 'string') && attr.name === exAttr.name),
|
|
44
|
-
).toStrictEqual(exAttr2);
|
|
37
|
+
expect(aElAttrs['extended-attr']).toStrictEqual(exAttr2);
|
|
45
38
|
});
|
|
46
39
|
});
|
package/src/get-spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ElementSpec, ExtendedSpec, MLMLSpec } from './types';
|
|
1
|
+
import type { ElementSpec, ExtendedSpec, MLMLSpec, Attribute } from './types';
|
|
2
2
|
|
|
3
3
|
import { mergeArray } from './utils';
|
|
4
4
|
|
|
@@ -21,10 +21,10 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
|
|
|
21
21
|
result.def['#ariaAttrs'] = [...result.def['#ariaAttrs'], ...extendedSpec.def['#ariaAttrs']];
|
|
22
22
|
}
|
|
23
23
|
if (extendedSpec.def['#globalAttrs']?.['#extends']) {
|
|
24
|
-
result.def['#globalAttrs']['#HTMLGlobalAttrs'] =
|
|
25
|
-
...(result.def['#globalAttrs']?.['#HTMLGlobalAttrs'] ||
|
|
26
|
-
...(extendedSpec.def['#globalAttrs']?.['#extends'] ||
|
|
27
|
-
|
|
24
|
+
result.def['#globalAttrs']['#HTMLGlobalAttrs'] = {
|
|
25
|
+
...(result.def['#globalAttrs']?.['#HTMLGlobalAttrs'] || {}),
|
|
26
|
+
...(extendedSpec.def['#globalAttrs']?.['#extends'] || {}),
|
|
27
|
+
};
|
|
28
28
|
}
|
|
29
29
|
if (extendedSpec.def['#roles']) {
|
|
30
30
|
result.def['#roles'] = [...result.def['#roles'], ...extendedSpec.def['#roles']];
|
|
@@ -55,7 +55,11 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
|
|
|
55
55
|
specs.push({
|
|
56
56
|
...elSpec,
|
|
57
57
|
...exSpec,
|
|
58
|
-
|
|
58
|
+
globalAttrs: {
|
|
59
|
+
...elSpec.globalAttrs,
|
|
60
|
+
...exSpec.globalAttrs,
|
|
61
|
+
},
|
|
62
|
+
attributes: mergeAttrSpec(elSpec.attributes, exSpec.attributes),
|
|
59
63
|
categories: mergeArray(elSpec.categories, exSpec.categories),
|
|
60
64
|
});
|
|
61
65
|
}
|
|
@@ -66,3 +70,20 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
|
|
|
66
70
|
|
|
67
71
|
return result;
|
|
68
72
|
}
|
|
73
|
+
|
|
74
|
+
function mergeAttrSpec(
|
|
75
|
+
std: Record<string, Attribute>,
|
|
76
|
+
ex: Record<string, Partial<Attribute>> = {},
|
|
77
|
+
): Record<string, Attribute> {
|
|
78
|
+
const result: Record<string, Attribute> = {};
|
|
79
|
+
const keys = Array.from(new Set([...Object.keys(std), ...Object.keys(ex)]));
|
|
80
|
+
for (const key of keys) {
|
|
81
|
+
const _std = std[key];
|
|
82
|
+
const _ex = ex[key];
|
|
83
|
+
result[key] = {
|
|
84
|
+
..._std,
|
|
85
|
+
..._ex,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -57,6 +57,12 @@ export interface PermittedStructuresSchema {
|
|
|
57
57
|
}
|
|
58
58
|
| {
|
|
59
59
|
parent: string;
|
|
60
|
+
/**
|
|
61
|
+
* Not support yet
|
|
62
|
+
*/
|
|
63
|
+
hasNotAttr?: string;
|
|
64
|
+
_TODO_?: string;
|
|
65
|
+
_parent?: string;
|
|
60
66
|
};
|
|
61
67
|
contents: PermittedContentSpec | boolean;
|
|
62
68
|
}[];
|
|
@@ -69,24 +75,28 @@ export interface PermittedContentRequire {
|
|
|
69
75
|
notAllowedDescendants?: Node[];
|
|
70
76
|
max?: number;
|
|
71
77
|
min?: number;
|
|
78
|
+
_TODO_?: string;
|
|
72
79
|
}
|
|
73
80
|
export interface PermittedContentOptional {
|
|
74
81
|
optional: Target;
|
|
75
82
|
ignore?: Target;
|
|
76
83
|
notAllowedDescendants?: Node[];
|
|
77
84
|
max?: number;
|
|
85
|
+
_TODO_?: string;
|
|
78
86
|
}
|
|
79
87
|
export interface PermittedContentOneOrMore {
|
|
80
88
|
oneOrMore: Target | PermittedContentSpec;
|
|
81
89
|
ignore?: Target;
|
|
82
90
|
notAllowedDescendants?: Node[];
|
|
83
91
|
max?: number;
|
|
92
|
+
_TODO_?: string;
|
|
84
93
|
}
|
|
85
94
|
export interface PermittedContentZeroOrMore {
|
|
86
95
|
zeroOrMore: Target | PermittedContentSpec;
|
|
87
96
|
ignore?: Target;
|
|
88
97
|
notAllowedDescendants?: Node[];
|
|
89
98
|
max?: number;
|
|
99
|
+
_TODO_?: string;
|
|
90
100
|
}
|
|
91
101
|
export interface PermittedContentChoice {
|
|
92
102
|
choice:
|
|
@@ -100,7 +110,9 @@ export interface PermittedContentChoice {
|
|
|
100
110
|
PermittedContentSpec,
|
|
101
111
|
PermittedContentSpec,
|
|
102
112
|
];
|
|
113
|
+
_TODO_?: string;
|
|
103
114
|
}
|
|
104
115
|
export interface PermittedContentInterleave {
|
|
105
116
|
interleave: [PermittedContentSpec, PermittedContentSpec, ...PermittedContentSpec[]];
|
|
117
|
+
_TODO_?: string;
|
|
106
118
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AttributeJSON } from '.';
|
|
2
|
+
import type { AttributeType, GlobalAttributes } from './attributes';
|
|
2
3
|
import type { ContentModel, PermittedStructuresSchema } from './permitted-structres';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -10,7 +11,10 @@ export interface MLMLSpec {
|
|
|
10
11
|
specs: ElementSpec[];
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
type ExtendedElementSpec = Partial<ElementSpec
|
|
14
|
+
export type ExtendedElementSpec = Partial<Omit<ElementSpec, 'name' | 'attributes'>> & {
|
|
15
|
+
name: ElementSpec['name'];
|
|
16
|
+
attributes?: Record<string, Partial<Attribute>>;
|
|
17
|
+
};
|
|
14
18
|
|
|
15
19
|
export type ExtendedSpec = {
|
|
16
20
|
cites?: Cites;
|
|
@@ -25,9 +29,9 @@ export type Cites = string[];
|
|
|
25
29
|
|
|
26
30
|
export type SpecDefs = {
|
|
27
31
|
'#globalAttrs': Partial<{
|
|
28
|
-
'#extends': Attribute
|
|
29
|
-
'#HTMLGlobalAttrs': Attribute
|
|
30
|
-
[OtherGlobalAttrs: string]: Attribute
|
|
32
|
+
'#extends': Record<string, Partial<Attribute>>;
|
|
33
|
+
'#HTMLGlobalAttrs': Record<string, Partial<Attribute>>;
|
|
34
|
+
[OtherGlobalAttrs: string]: Record<string, Partial<Attribute>>;
|
|
31
35
|
}>;
|
|
32
36
|
'#ariaAttrs': ARIAAttribute[];
|
|
33
37
|
'#roles': ARIRRoleAttribute[];
|
|
@@ -122,10 +126,15 @@ export type ElementSpec = {
|
|
|
122
126
|
*/
|
|
123
127
|
omittion: ElementSpecOmittion;
|
|
124
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Global Attributes
|
|
131
|
+
*/
|
|
132
|
+
globalAttrs: GlobalAttributes;
|
|
133
|
+
|
|
125
134
|
/**
|
|
126
135
|
* Attributes
|
|
127
136
|
*/
|
|
128
|
-
attributes:
|
|
137
|
+
attributes: Record<string, Attribute>;
|
|
129
138
|
};
|
|
130
139
|
|
|
131
140
|
/**
|
|
@@ -152,18 +161,16 @@ type ElementCondition = {
|
|
|
152
161
|
|
|
153
162
|
export type Attribute = {
|
|
154
163
|
name: string;
|
|
155
|
-
type: AttributeType |
|
|
156
|
-
description
|
|
164
|
+
type: AttributeType | AttributeType[];
|
|
165
|
+
description?: string;
|
|
157
166
|
caseSensitive?: true;
|
|
158
167
|
experimental?: true;
|
|
159
168
|
obsolete?: true;
|
|
160
169
|
deprecated?: boolean;
|
|
161
170
|
nonStandard?: true;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
condition?: AttributeCondition;
|
|
166
|
-
};
|
|
171
|
+
} & ExtendableAttributeSpec;
|
|
172
|
+
|
|
173
|
+
type ExtendableAttributeSpec = Omit<AttributeJSON, 'ref' | '_TODO_' | 'type'>;
|
|
167
174
|
|
|
168
175
|
export type ARIRRoleAttribute = {
|
|
169
176
|
name: string;
|