@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.
@@ -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 { Attribute, MLDOMElementSpec, MLMLSpec, SpecOM } from './types';
1
+ import type { MLDOMElementSpec, MLMLSpec, SpecOM } from './types';
2
2
 
3
- function getSpecOM({ specs }: MLMLSpec): SpecOM {
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: el.attributes.filter(
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
 
@@ -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: Attribute = {
10
- name: 'extended-attr',
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: Attribute = {
15
- name: 'extended-attr',
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: [exAttr],
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: [exAttr2],
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
- attributes: mergeArray(elSpec.attributes, exSpec.attributes),
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
@@ -1,5 +1,6 @@
1
1
  export * from './permitted-structres';
2
2
  export * from './attributes';
3
+ export * from './get-attr-specs';
3
4
  export * from './get-spec-by-tag-name';
4
5
  export * from './get-spec';
5
6
  export * from './get-ns';
@@ -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 { AttributeCondition, AttributeType } from './attributes';
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> & { name: ElementSpec['name'] };
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: (Attribute | string)[];
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 | [AttributeType, ...AttributeType[]];
156
- description: string;
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
- required?: boolean | AttributeCondition;
163
- requiredEither?: string[];
164
- noUse?: boolean;
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;