@markuplint/ml-spec 1.7.0 → 2.0.0-dev.20211213.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.
@@ -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,97 @@
1
+ import type { MLMLSpec, Attribute } from './types';
2
+
3
+ import path from 'path';
4
+
5
+ export function getAttrSpecs(nameWithNS: string, { specs, def }: MLMLSpec) {
6
+ const elSpec = specs.find(spec => spec.name === nameWithNS);
7
+
8
+ if (!elSpec) {
9
+ return null;
10
+ }
11
+ const globalAttrs = def['#globalAttrs'];
12
+ let attrs: Record<string, Partial<Attribute>> = {};
13
+
14
+ for (const catName in elSpec.globalAttrs) {
15
+ // @ts-ignore
16
+ const catAttrs: boolean | string[] = elSpec.globalAttrs[catName];
17
+ if (!catAttrs) {
18
+ continue;
19
+ }
20
+ if (typeof catAttrs === 'boolean') {
21
+ const global = globalAttrs[catName];
22
+ attrs = {
23
+ ...attrs,
24
+ ...global,
25
+ };
26
+ continue;
27
+ }
28
+ if (Array.isArray(catAttrs)) {
29
+ const global = globalAttrs[catName];
30
+ if (!global) {
31
+ continue;
32
+ }
33
+ catAttrs.forEach(selectedName => {
34
+ const selected = global[selectedName];
35
+ attrs[selectedName] = {
36
+ ...attrs[selectedName],
37
+ ...selected,
38
+ };
39
+ });
40
+ continue;
41
+ }
42
+ }
43
+
44
+ for (const attrName in elSpec.attributes) {
45
+ const attr = elSpec.attributes[attrName];
46
+ if (!attr) {
47
+ continue;
48
+ }
49
+
50
+ const current = attrs[attrName] as Omit<Attribute, 'name'> | undefined;
51
+
52
+ attrs[attrName] = {
53
+ description: '',
54
+ ...current,
55
+ ...attr,
56
+ };
57
+ }
58
+
59
+ const attrList: Attribute[] = Object.keys(attrs).map<Attribute>(name => {
60
+ const attr = attrs[name];
61
+ return { name, type: 'Any', ...attr };
62
+ });
63
+
64
+ for (const attr of attrList) {
65
+ if (!attr.type) {
66
+ throw new Error(
67
+ `The type is empty in the ${attr.name} attribute of the ${nameWithNS} element (${path.resolve(
68
+ process.cwd(),
69
+ 'packages',
70
+ '@markuplint',
71
+ 'html-spec',
72
+ 'src',
73
+ 'attributes',
74
+ nameWithNS.replace(':', '_') + '.json',
75
+ )})`,
76
+ );
77
+ }
78
+ }
79
+
80
+ attrList.sort(nameCompare);
81
+
82
+ return attrList;
83
+ }
84
+
85
+ type HasName = { name: string };
86
+
87
+ export function nameCompare(a: HasName | string, b: HasName | string) {
88
+ const nameA = typeof a === 'string' ? a : a.name.toUpperCase();
89
+ const nameB = typeof b === 'string' ? b : b.name.toUpperCase();
90
+ if (nameA < nameB) {
91
+ return -1;
92
+ }
93
+ if (nameA > nameB) {
94
+ return 1;
95
+ }
96
+ return 0;
97
+ }
package/src/get-ns.ts ADDED
@@ -0,0 +1,13 @@
1
+ export function getNS(namespaceURI: string) {
2
+ switch (namespaceURI) {
3
+ case 'http://www.w3.org/2000/svg': {
4
+ return 'svg';
5
+ }
6
+ case 'http://www.w3.org/1998/Math/MathML': {
7
+ return 'mml';
8
+ }
9
+ default: {
10
+ return 'html';
11
+ }
12
+ }
13
+ }
@@ -1,8 +1,11 @@
1
- import { 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
+ function getSpecOM(spec: MLMLSpec): SpecOM {
4
6
  const som: SpecOM = {};
5
- for (const el of specs) {
7
+ for (const el of spec.specs) {
8
+ const attributes = getAttrSpecs(el.name, spec);
6
9
  som[el.name] = {
7
10
  experimental: !!el.experimental,
8
11
  obsolete: typeof el.obsolete === 'boolean' ? !!el.obsolete : el.obsolete ? el.obsolete.alt : false,
@@ -10,16 +13,13 @@ function getSpecOM({ specs }: MLMLSpec): SpecOM {
10
13
  nonStandard: !!el.nonStandard,
11
14
  categories: el.categories,
12
15
  permittedStructures: el.permittedStructures,
13
- attributes: el.attributes.filter(
14
- (attr: Attribute | string): attr is Attribute => !(typeof attr === 'string'),
15
- ),
16
+ attributes: Object.values(attributes || {}),
16
17
  };
17
18
  }
18
19
  return som;
19
20
  }
20
21
 
21
- export function getSpecByTagName(tagName: string, specs: MLMLSpec): MLDOMElementSpec | null {
22
+ export function getSpecByTagName(nameWithNS: string, specs: MLMLSpec): MLDOMElementSpec | null {
22
23
  const specOM = getSpecOM(specs);
23
- tagName = tagName.toLowerCase();
24
- return specOM[tagName] || null;
24
+ return specOM[nameWithNS] || null;
25
25
  }
@@ -1,25 +1,26 @@
1
- import htmlSpec, { Attribute } from '@markuplint/html-spec';
1
+ import htmlSpec from '@markuplint/html-spec';
2
+
2
3
  import { getSpec } from './get-spec';
3
4
 
4
5
  describe('getSpec', () => {
5
- test('', () => {
6
- const exAttr: Attribute = {
7
- name: 'extended-attr',
6
+ test('Overriding', () => {
7
+ const exAttr = {
8
+ ref: 'N/A',
8
9
  type: 'Boolean',
9
10
  description: 'For the unit test.',
10
- };
11
- const exAttr2: Attribute = {
12
- name: 'extended-attr',
11
+ } as const;
12
+ const exAttr2 = {
13
+ ref: 'N/A',
13
14
  type: 'Boolean',
14
15
  description: 'For the unit test. Override.',
15
- };
16
+ } as const;
16
17
  const mergedSpec = getSpec([
17
18
  htmlSpec,
18
19
  {
19
20
  specs: [
20
21
  {
21
22
  name: 'a',
22
- attributes: [exAttr],
23
+ attributes: { 'extended-attr': exAttr },
23
24
  },
24
25
  ],
25
26
  },
@@ -27,17 +28,12 @@ describe('getSpec', () => {
27
28
  specs: [
28
29
  {
29
30
  name: 'a',
30
- attributes: [exAttr2],
31
+ attributes: { 'extended-attr': exAttr2 },
31
32
  },
32
33
  ],
33
34
  },
34
35
  ]);
35
36
  const aElAttrs = mergedSpec.specs.find(el => el.name === 'a')!.attributes;
36
- expect(
37
- aElAttrs.find((attr): attr is Attribute => !(typeof attr === 'string') && attr.name === 'href')!.name,
38
- ).toBe('href');
39
- expect(
40
- aElAttrs.find((attr): attr is Attribute => !(typeof attr === 'string') && attr.name === exAttr.name),
41
- ).toStrictEqual(exAttr2);
37
+ expect(aElAttrs['extended-attr']).toStrictEqual(exAttr2);
42
38
  });
43
39
  });
package/src/get-spec.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { ElementSpec, ExtendedSpec, MLMLSpec } from '@markuplint/ml-spec';
1
+ import type { ElementSpec, ExtendedSpec, MLMLSpec, Attribute } from './types';
2
+
2
3
  import { mergeArray } from './utils';
3
4
 
4
5
  /**
@@ -19,8 +20,11 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
19
20
  if (extendedSpec.def['#ariaAttrs']) {
20
21
  result.def['#ariaAttrs'] = [...result.def['#ariaAttrs'], ...extendedSpec.def['#ariaAttrs']];
21
22
  }
22
- if (extendedSpec.def['#globalAttrs']) {
23
- result.def['#globalAttrs'] = [...result.def['#globalAttrs'], ...extendedSpec.def['#globalAttrs']];
23
+ if (extendedSpec.def['#globalAttrs']?.['#extends']) {
24
+ result.def['#globalAttrs']['#HTMLGlobalAttrs'] = {
25
+ ...(result.def['#globalAttrs']?.['#HTMLGlobalAttrs'] || {}),
26
+ ...(extendedSpec.def['#globalAttrs']?.['#extends'] || {}),
27
+ };
24
28
  }
25
29
  if (extendedSpec.def['#roles']) {
26
30
  result.def['#roles'] = [...result.def['#roles'], ...extendedSpec.def['#roles']];
@@ -51,7 +55,11 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
51
55
  specs.push({
52
56
  ...elSpec,
53
57
  ...exSpec,
54
- attributes: mergeArray(elSpec.attributes, exSpec.attributes),
58
+ globalAttrs: {
59
+ ...elSpec.globalAttrs,
60
+ ...exSpec.globalAttrs,
61
+ },
62
+ attributes: mergeAttrSpec(elSpec.attributes, exSpec.attributes),
55
63
  categories: mergeArray(elSpec.categories, exSpec.categories),
56
64
  });
57
65
  }
@@ -62,3 +70,20 @@ export function getSpec(schemas: readonly [MLMLSpec, ...ExtendedSpec[]]) {
62
70
 
63
71
  return result;
64
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,4 +1,7 @@
1
1
  export * from './permitted-structres';
2
+ export * from './attributes';
3
+ export * from './get-attr-specs';
2
4
  export * from './get-spec-by-tag-name';
3
5
  export * from './get-spec';
6
+ export * from './get-ns';
4
7
  export * from './types';
@@ -24,7 +24,27 @@ export type ContentModel =
24
24
  | '#transparent'
25
25
  | '#embedded'
26
26
  | '#palpable'
27
- | '#script-supporting';
27
+ | '#script-supporting'
28
+ | '#SVGAnimation'
29
+ | '#SVGBasicShapes'
30
+ | '#SVGContainer'
31
+ | '#SVGDescriptive'
32
+ | '#SVGFilterPrimitive'
33
+ | '#SVGFont'
34
+ | '#SVGGradient'
35
+ | '#SVGGraphics'
36
+ | '#SVGGraphicsReferencing'
37
+ | '#SVGLightSource'
38
+ | '#SVGNeverRendered'
39
+ | '#SVGNone'
40
+ | '#SVGPaintServer'
41
+ | '#SVGRenderable'
42
+ | '#SVGShape'
43
+ | '#SVGStructural'
44
+ | '#SVGStructurallyExternal'
45
+ | '#SVGTextContent'
46
+ | '#SVGTextContentChild'
47
+ | '#SVGOtherXMLNamespace';
28
48
  export type PermittedContentSpec = PermittedContent[];
29
49
 
30
50
  export interface PermittedStructuresSchema {
@@ -37,6 +57,12 @@ export interface PermittedStructuresSchema {
37
57
  }
38
58
  | {
39
59
  parent: string;
60
+ /**
61
+ * Not support yet
62
+ */
63
+ hasNotAttr?: string;
64
+ _TODO_?: string;
65
+ _parent?: string;
40
66
  };
41
67
  contents: PermittedContentSpec | boolean;
42
68
  }[];
@@ -49,24 +75,28 @@ export interface PermittedContentRequire {
49
75
  notAllowedDescendants?: Node[];
50
76
  max?: number;
51
77
  min?: number;
78
+ _TODO_?: string;
52
79
  }
53
80
  export interface PermittedContentOptional {
54
81
  optional: Target;
55
82
  ignore?: Target;
56
83
  notAllowedDescendants?: Node[];
57
84
  max?: number;
85
+ _TODO_?: string;
58
86
  }
59
87
  export interface PermittedContentOneOrMore {
60
88
  oneOrMore: Target | PermittedContentSpec;
61
89
  ignore?: Target;
62
90
  notAllowedDescendants?: Node[];
63
91
  max?: number;
92
+ _TODO_?: string;
64
93
  }
65
94
  export interface PermittedContentZeroOrMore {
66
95
  zeroOrMore: Target | PermittedContentSpec;
67
96
  ignore?: Target;
68
97
  notAllowedDescendants?: Node[];
69
98
  max?: number;
99
+ _TODO_?: string;
70
100
  }
71
101
  export interface PermittedContentChoice {
72
102
  choice:
@@ -80,7 +110,9 @@ export interface PermittedContentChoice {
80
110
  PermittedContentSpec,
81
111
  PermittedContentSpec,
82
112
  ];
113
+ _TODO_?: string;
83
114
  }
84
115
  export interface PermittedContentInterleave {
85
116
  interleave: [PermittedContentSpec, PermittedContentSpec, ...PermittedContentSpec[]];
117
+ _TODO_?: string;
86
118
  }
package/src/types.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { ContentModel, PermittedStructuresSchema } from './permitted-structres';
1
+ import type { AttributeJSON } from '.';
2
+ import type { AttributeType, GlobalAttributes } from './attributes';
3
+ import type { ContentModel, PermittedStructuresSchema } from './permitted-structres';
2
4
 
3
5
  /**
4
6
  * markuplit Markup-language spec
@@ -9,7 +11,10 @@ export interface MLMLSpec {
9
11
  specs: ElementSpec[];
10
12
  }
11
13
 
12
- 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
+ };
13
18
 
14
19
  export type ExtendedSpec = {
15
20
  cites?: Cites;
@@ -23,9 +28,13 @@ export type ExtendedSpec = {
23
28
  export type Cites = string[];
24
29
 
25
30
  export type SpecDefs = {
26
- '#globalAttrs': Attribute[];
27
- '#roles': ARIRRoleAttribute[];
31
+ '#globalAttrs': Partial<{
32
+ '#extends': Record<string, Partial<Attribute>>;
33
+ '#HTMLGlobalAttrs': Record<string, Partial<Attribute>>;
34
+ [OtherGlobalAttrs: string]: Record<string, Partial<Attribute>>;
35
+ }>;
28
36
  '#ariaAttrs': ARIAAttribute[];
37
+ '#roles': ARIRRoleAttribute[];
29
38
  '#contentModels': { [model in ContentModel]?: string[] };
30
39
  };
31
40
 
@@ -38,6 +47,12 @@ export type ElementSpec = {
38
47
  */
39
48
  name: string;
40
49
 
50
+ /**
51
+ * Namespaces in XML
52
+ * @see https://www.w3.org/TR/xml-names/
53
+ */
54
+ namespace?: 'http://www.w3.org/1999/xhtml' | 'http://www.w3.org/2000/svg' | 'http://www.w3.org/1998/Math/MathML';
55
+
41
56
  /**
42
57
  * Reference URL
43
58
  */
@@ -111,10 +126,15 @@ export type ElementSpec = {
111
126
  */
112
127
  omittion: ElementSpecOmittion;
113
128
 
129
+ /**
130
+ * Global Attributes
131
+ */
132
+ globalAttrs: GlobalAttributes;
133
+
114
134
  /**
115
135
  * Attributes
116
136
  */
117
- attributes: (Attribute | string)[];
137
+ attributes: Record<string, Attribute>;
118
138
  };
119
139
 
120
140
  /**
@@ -141,63 +161,16 @@ type ElementCondition = {
141
161
 
142
162
  export type Attribute = {
143
163
  name: string;
144
- type: AttributeType;
145
- description: string;
164
+ type: AttributeType | AttributeType[];
165
+ description?: string;
146
166
  caseSensitive?: true;
147
167
  experimental?: true;
148
168
  obsolete?: true;
149
- deprecated?: true;
169
+ deprecated?: boolean;
150
170
  nonStandard?: true;
151
- required?: true | AttributeCondition;
152
- requiredEither?: string[];
153
- enum?: string[];
154
- noUse?: boolean;
155
- condition?: AttributeCondition;
156
- };
157
-
158
- export type AttributeCondition = {
159
- ancestor?: string;
160
- self?: string | string[];
161
- };
171
+ } & ExtendableAttributeSpec;
162
172
 
163
- // type AttributeCtegory = 'global' | 'xml' | 'aria' | 'eventhandler' | 'form' | 'particular';
164
-
165
- export type AttributeType =
166
- | 'String'
167
- | 'NonEmptyString'
168
- | 'Boolean'
169
- | 'Function' // JavaScript function body
170
- | 'Date'
171
- | 'Int' // Integer
172
- | 'Uint' // Non-negative integer
173
- | 'Float' // Floating-point number
174
- | 'NonZeroUint' // Non-negative integer greater than zero
175
- | 'AcceptList' // https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
176
- | 'AutoComplete' // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-detail-tokens
177
- | 'BCP47' // https://tools.ietf.org/html/bcp47
178
- | 'Color' // https://drafts.csswg.org/css-color/#typedef-color
179
- | 'ColSpan' // https://html.spec.whatwg.org/multipage/tables.html#attr-tdth-colspan
180
- | 'Coords' // https://html.spec.whatwg.org/multipage/image-maps.html#attr-area-coords
181
- | 'DateTime' // https://html.spec.whatwg.org/multipage/text-level-semantics.html#datetime-value
182
- | 'Destination' // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-as
183
- | 'DOMID'
184
- | 'DOMIDList'
185
- | 'ItemType' // https://html.spec.whatwg.org/multipage/microdata.html#attr-itemtype
186
- | 'LinkSizes' // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
187
- | 'LinkType' // https://html.spec.whatwg.org/multipage/links.html#linkTypes
188
- | 'LinkTypeList' // https://html.spec.whatwg.org/multipage/links.html#linkTypes
189
- | 'MediaQuery' // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-media-query-list
190
- | 'MediaQueryList' // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-media-query-list
191
- | 'MIMEType' // https://mimesniff.spec.whatwg.org/#valid-mime-type
192
- | 'ReferrerPolicy' // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#referrer-policy-attributes
193
- | 'RowSpan' // https://html.spec.whatwg.org/multipage/tables.html#attr-tdth-rowspan
194
- | 'SourceSizeList' // https://html.spec.whatwg.org/multipage/images.html#sizes-attributes
195
- | 'SrcSet' // https://html.spec.whatwg.org/multipage/images.html#srcset-attribute
196
- | 'TabIndex' // https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
197
- | 'Target' // https://html.spec.whatwg.org/multipage/links.html#attr-hyperlink-target
198
- | 'URL' // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-url-potentially-surrounded-by-spaces
199
- | 'URLHash' // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-hash-name-reference
200
- | 'URLList'; // https://html.spec.whatwg.org/multipage/urls-and-fetching.html#valid-url-potentially-surrounded-by-spaces
173
+ type ExtendableAttributeSpec = Omit<AttributeJSON, 'ref' | '_TODO_' | 'type'>;
201
174
 
202
175
  export type ARIRRoleAttribute = {
203
176
  name: string;
@@ -1,8 +1,3 @@
1
1
  {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "composite": true
5
- },
6
- "include": ["./src/**/*"],
7
- "exclude": ["node_modules"]
2
+ "extends": "../../../tsconfig.json"
8
3
  }