@markuplint/ml-spec 2.0.0-dev.26 → 2.0.0-rc.5
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 +25 -13
- package/package.json +6 -4
- package/schemas/attributes.schema.json +168 -181
- package/schemas/global-attributes.schema.json +749 -6
- package/schemas/permitted-structures.schema.json +19 -7
- package/src/attributes.ts +1393 -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 +30 -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,25 @@ 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>;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* If true, it is possible to add any properties as attributes,
|
|
141
|
+
* for example, when using a template engine or a view language.
|
|
142
|
+
*
|
|
143
|
+
* @see https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
|
|
144
|
+
*
|
|
145
|
+
* **It assumes to specify it on the parser plugin.**
|
|
146
|
+
*/
|
|
147
|
+
possibleToAddProperties?: true;
|
|
129
148
|
};
|
|
130
149
|
|
|
131
150
|
/**
|
|
@@ -152,18 +171,16 @@ type ElementCondition = {
|
|
|
152
171
|
|
|
153
172
|
export type Attribute = {
|
|
154
173
|
name: string;
|
|
155
|
-
type: AttributeType |
|
|
156
|
-
description
|
|
174
|
+
type: AttributeType | AttributeType[];
|
|
175
|
+
description?: string;
|
|
157
176
|
caseSensitive?: true;
|
|
158
177
|
experimental?: true;
|
|
159
178
|
obsolete?: true;
|
|
160
179
|
deprecated?: boolean;
|
|
161
180
|
nonStandard?: true;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
condition?: AttributeCondition;
|
|
166
|
-
};
|
|
181
|
+
} & ExtendableAttributeSpec;
|
|
182
|
+
|
|
183
|
+
type ExtendableAttributeSpec = Omit<AttributeJSON, 'ref' | '_TODO_' | 'type'>;
|
|
167
184
|
|
|
168
185
|
export type ARIRRoleAttribute = {
|
|
169
186
|
name: string;
|