web-components-doctor 0.1.0 → 0.3.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.
package/README.md CHANGED
@@ -10,6 +10,8 @@ npm install web-components-doctor --save-dev
10
10
 
11
11
  ## Quick Start (ESLint 9+ flat config)
12
12
 
13
+ ### Lit (html tagged templates)
14
+
13
15
  ```js
14
16
  // eslint.config.js
15
17
  import swc from 'web-components-doctor';
@@ -19,6 +21,34 @@ export default [
19
21
  ];
20
22
  ```
21
23
 
24
+ ### JSX / TSX (React, Preact, etc.)
25
+
26
+ The plugin automatically detects both syntaxes. Just enable JSX parsing in your ESLint config:
27
+
28
+ ```js
29
+ // eslint.config.js
30
+ import swc from 'web-components-doctor';
31
+
32
+ export default [
33
+ {
34
+ ...swc.configs.recommended,
35
+ languageOptions: {
36
+ parserOptions: {
37
+ ecmaFeatures: { jsx: true },
38
+ },
39
+ },
40
+ },
41
+ ];
42
+ ```
43
+
44
+ Both kebab-case custom elements and PascalCase React wrappers are supported:
45
+
46
+ ```jsx
47
+ // These are equivalent — both are checked
48
+ <sp-action-menu label="Actions"></sp-action-menu>
49
+ <SpActionMenu label="Actions"></SpActionMenu>
50
+ ```
51
+
22
52
  Or for strict CI enforcement:
23
53
 
24
54
  ```js
@@ -34,13 +64,21 @@ export default [
34
64
  Require accessibility attributes on SWC elements.
35
65
 
36
66
  ```js
37
- // ❌ Bad
67
+ // ❌ Bad — Lit
38
68
  html`<sp-action-menu></sp-action-menu>`;
39
69
  html`<sp-picker></sp-picker>`;
40
70
 
41
- // Good
71
+ // Bad — JSX
72
+ <SpActionMenu></SpActionMenu>
73
+ <SpPicker></SpPicker>
74
+
75
+ // ✅ Good — Lit
42
76
  html`<sp-action-menu label="More actions"></sp-action-menu>`;
43
77
  html`<sp-picker aria-label="Select option"></sp-picker>`;
78
+
79
+ // ✅ Good — JSX
80
+ <SpActionMenu label="More actions"></SpActionMenu>
81
+ <SpPicker aria-label="Select option"></SpPicker>
44
82
  ```
45
83
 
46
84
  | Element | Required (at least one of) |
@@ -60,13 +98,21 @@ html`<sp-picker aria-label="Select option"></sp-picker>`;
60
98
  Flag deprecated attributes and attribute values.
61
99
 
62
100
  ```js
63
- // ❌ Bad
101
+ // ❌ Bad — Lit
64
102
  html`<sp-button variant="cta">Click</sp-button>`;
65
103
  html`<sp-overlay allow-outside-click></sp-overlay>`;
66
104
 
67
- // Good
105
+ // Bad — JSX
106
+ <SpButton variant="cta">Click</SpButton>
107
+ <SpOverlay allowOutsideClick></SpOverlay>
108
+
109
+ // ✅ Good — Lit
68
110
  html`<sp-button variant="accent">Click</sp-button>`;
69
111
  html`<sp-overlay></sp-overlay>`;
112
+
113
+ // ✅ Good — JSX
114
+ <SpButton variant="accent">Click</SpButton>
115
+ <SpOverlay></SpOverlay>
70
116
  ```
71
117
 
72
118
  | Deprecated | Replacement |
@@ -106,21 +152,90 @@ html`<sp-theme color="light"></sp-theme>`;
106
152
  html`<sp-button variant="accent">Click</sp-button>`;
107
153
  ```
108
154
 
155
+ ### `swc/valid-slot-names`
156
+
157
+ Warn when a child element targets a slot that the parent component doesn't define.
158
+
159
+ ```js
160
+ // ❌ Bad — Lit
161
+ html`<sp-action-menu label="Actions">
162
+ <sp-menu-item slot="header">Edit</sp-menu-item>
163
+ </sp-action-menu>`;
164
+
165
+ // ❌ Bad — JSX
166
+ <SpActionMenu label="Actions">
167
+ <SpMenuItem slot="header">Edit</SpMenuItem>
168
+ </SpActionMenu>
169
+
170
+ // ✅ Good — Lit
171
+ html`<sp-action-menu label="Actions">
172
+ <sp-menu-item>Edit</sp-menu-item>
173
+ <sp-icon slot="icon"></sp-icon>
174
+ </sp-action-menu>`;
175
+
176
+ // ✅ Good — JSX
177
+ <SpActionMenu label="Actions">
178
+ <SpMenuItem>Edit</SpMenuItem>
179
+ <SpIcon slot="icon"></SpIcon>
180
+ </SpActionMenu>
181
+ ```
182
+
183
+ ### `swc/valid-slot-children`
184
+
185
+ Warn when a child element's tag is not accepted in the slot it targets.
186
+
187
+ ```js
188
+ // ❌ Bad — Lit (sp-action-menu default slot only accepts sp-menu-item/group/divider)
189
+ html`<sp-action-menu label="Actions">
190
+ <sp-button>Wrong child</sp-button>
191
+ </sp-action-menu>`;
192
+
193
+ // ❌ Bad — JSX
194
+ <SpActionMenu label="Actions">
195
+ <SpButton>Wrong child</SpButton>
196
+ </SpActionMenu>
197
+
198
+ // ✅ Good — Lit
199
+ html`<sp-action-menu label="Actions">
200
+ <sp-menu-item>Edit</sp-menu-item>
201
+ <sp-menu-divider></sp-menu-divider>
202
+ <sp-menu-item>Delete</sp-menu-item>
203
+ </sp-action-menu>`;
204
+
205
+ // ✅ Good — JSX
206
+ <SpActionMenu label="Actions">
207
+ <SpMenuItem>Edit</SpMenuItem>
208
+ <SpMenuDivider></SpMenuDivider>
209
+ <SpMenuItem>Delete</SpMenuItem>
210
+ </SpActionMenu>
211
+ ```
212
+
213
+ | Component | Default slot accepts | Named slots |
214
+ |---|---|---|
215
+ | `<sp-action-menu>` | `sp-menu-item`, `sp-menu-group`, `sp-menu-divider` | `icon`, `label`, `tooltip` |
216
+ | `<sp-picker>` | `sp-menu-item`, `sp-menu-group`, `sp-menu-divider` | `label`, `tooltip`, `description` |
217
+ | `<sp-tabs>` | `sp-tab`, `sp-tab-panel` | — |
218
+ | `<sp-button>` | (any) | `icon` (accepts `sp-icon`) |
219
+ | `<sp-dialog-wrapper>` | (any) | `hero`, `heading`, `button` (accepts `sp-button`) |
220
+ | `<overlay-trigger>` | (any) | `click-content`, `hover-content`, `longpress-content` |
221
+
109
222
  ## Architecture
110
223
 
111
224
  This plugin is **data-driven**. Rules are generated from component descriptors rather than hand-written per component:
112
225
 
113
226
  ```
114
227
  src/
115
- ├── adapters/ # Template syntax parsers (Lit today, JSX/HTML next)
116
- └── lit-adapter.ts # Extracts elements from html`` tagged templates
228
+ ├── adapters/ # Template syntax parsers
229
+ ├── lit-adapter.ts # Extracts elements from html`` tagged templates
230
+ │ ├── jsx-adapter.ts # Extracts elements from JSX/TSX (PascalCase + kebab-case)
231
+ │ └── utils.ts # Shared utilities (pascalToKebab, camelToKebab, etc.)
117
232
  ├── core/
118
- │ ├── types.ts # Normalized IR (ParsedElement, descriptors)
119
- │ └── rule-factory.ts # Generates ESLint rules from descriptors
233
+ │ ├── types.ts # Normalized IR (ParsedElement, descriptors)
234
+ │ └── rule-factory.ts # Generates ESLint rules from descriptors (dual-visitor)
120
235
  ├── descriptors/
121
- │ └── components.ts # Component accessibility/deprecation metadata
122
- ├── rules/ # Thin wrappers: factory(descriptors)
123
- └── index.ts # Plugin entry with recommended/strict configs
236
+ │ └── components.ts # Component accessibility/deprecation metadata
237
+ ├── rules/ # Thin wrappers: factory(descriptors)
238
+ └── index.ts # Plugin entry with recommended/strict configs
124
239
  ```
125
240
 
126
241
  ### Adding a new component
@@ -136,14 +251,32 @@ Add an entry to `src/descriptors/components.ts`:
136
251
  validAttributeValues: {
137
252
  variant: ['primary', 'secondary'],
138
253
  },
254
+ slots: [
255
+ { name: '', acceptedChildren: ['sp-menu-item'] },
256
+ { name: 'icon', acceptedChildren: ['sp-icon'] },
257
+ ],
139
258
  }
140
259
  ```
141
260
 
142
261
  No new rule code needed — the rule factory picks it up automatically.
143
262
 
144
- ### Adding a new template syntax (e.g. JSX)
263
+ ### JSX naming conventions
264
+
265
+ The JSX adapter supports two usage patterns:
145
266
 
146
- Create a new adapter in `src/adapters/` that returns `ParsedElement[]` from the relevant AST node type. The rule logic is template-agnostic.
267
+ | Pattern | Example | Resolves to |
268
+ |---|---|---|
269
+ | Kebab-case (direct CE usage) | `<sp-action-menu>` | `sp-action-menu` |
270
+ | PascalCase (React wrapper) | `<SpActionMenu>` | `sp-action-menu` |
271
+
272
+ CamelCase props are automatically converted to their kebab-case attribute equivalents:
273
+ - `ariaLabel` → `aria-label`
274
+ - `isDecorative` → `is-decorative`
275
+ - `allowOutsideClick` → `allow-outside-click`
276
+
277
+ ### Adding a new template syntax
278
+
279
+ Create a new adapter in `src/adapters/` that returns `ParsedElement[]` from the relevant AST node type, then register its visitor in `rule-factory.ts`. The rule logic is template-agnostic.
147
280
 
148
281
  ## Configs
149
282
 
@@ -157,8 +290,11 @@ Create a new adapter in `src/adapters/` that returns `ParsedElement[]` from the
157
290
  The plugin **skips** attributes with dynamic template expressions since they can't be statically verified:
158
291
 
159
292
  ```js
160
- // No warning — value is dynamic
293
+ // No warning — Lit dynamic value
161
294
  html`<sp-action-menu label=${this.label}></sp-action-menu>`;
295
+
296
+ // No warning — JSX dynamic value
297
+ <SpActionMenu label={this.label}></SpActionMenu>
162
298
  ```
163
299
 
164
300
  ## Related
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import type { Rule } from 'eslint';
13
+ import type { ParsedElement } from '../core/types.js';
14
+ /**
15
+ * Extract a ParsedElement from a JSXElement AST node.
16
+ * Returns an empty array if not an SWC element.
17
+ * Recursively extracts direct children for slot validation.
18
+ */
19
+ export declare function extractElementFromJSX(node: Rule.Node): ParsedElement[];
20
+ //# sourceMappingURL=jsx-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/jsx-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsKtE;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,GAAG,aAAa,EAAE,CAkCtE"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import { camelToKebab, isSwcJsxTag, resolveJsxTagName } from './utils.js';
13
+ /**
14
+ * Resolve a JSX element's name node to a plain string.
15
+ * Handles both `<SpButton>` (JSXIdentifier) and `<Sp.Button>` (JSXMemberExpression).
16
+ */
17
+ function getJsxElementName(nameNode) {
18
+ if (nameNode.type === 'JSXIdentifier') {
19
+ return nameNode.name;
20
+ }
21
+ if (nameNode.type === 'JSXMemberExpression') {
22
+ const parts = [];
23
+ let current = nameNode;
24
+ while (current.type === 'JSXMemberExpression') {
25
+ parts.unshift(current.property.name);
26
+ current = current.object;
27
+ }
28
+ parts.unshift(current.name);
29
+ return parts.join('.');
30
+ }
31
+ return null;
32
+ }
33
+ /**
34
+ * Determine if an attribute name is already kebab-case (contains a hyphen)
35
+ * meaning it doesn't need conversion.
36
+ */
37
+ function isKebabCase(name) {
38
+ return name.includes('-');
39
+ }
40
+ /**
41
+ * Normalize a JSX attribute name to its HTML attribute equivalent.
42
+ * If the prop is already kebab-case (e.g. `aria-label`), keep as-is.
43
+ * Otherwise convert camelCase to kebab-case.
44
+ */
45
+ function normalizeAttributeName(jsxPropName) {
46
+ if (isKebabCase(jsxPropName))
47
+ return jsxPropName;
48
+ return camelToKebab(jsxPropName);
49
+ }
50
+ /**
51
+ * Extract attributes from a JSXOpeningElement into a normalized map.
52
+ */
53
+ function extractAttributes(opening) {
54
+ const attributes = new Map();
55
+ for (const attr of opening.attributes) {
56
+ if (attr.type === 'JSXSpreadAttribute') {
57
+ continue;
58
+ }
59
+ const propName = attr.name.name;
60
+ const htmlName = normalizeAttributeName(propName);
61
+ if (attr.value === null) {
62
+ attributes.set(htmlName, { value: '', isDynamic: false });
63
+ }
64
+ else if (attr.value.type === 'Literal') {
65
+ const raw = attr.value.value === null ? '' : String(attr.value.value);
66
+ attributes.set(htmlName, { value: raw, isDynamic: false });
67
+ }
68
+ else if (attr.value.type === 'JSXExpressionContainer') {
69
+ attributes.set(htmlName, { value: null, isDynamic: true });
70
+ }
71
+ }
72
+ return attributes;
73
+ }
74
+ /**
75
+ * Recursively extract child ParsedElements from JSX children.
76
+ * Includes all element children so slot validation can check non-SWC tags too.
77
+ */
78
+ function extractChildElements(children) {
79
+ const results = [];
80
+ for (const child of children) {
81
+ if (child.type !== 'JSXElement' || !child.openingElement)
82
+ continue;
83
+ const rawName = getJsxElementName(child.openingElement.name);
84
+ if (!rawName)
85
+ continue;
86
+ const isSwc = isSwcJsxTag(rawName);
87
+ const tagName = isSwc ? resolveJsxTagName(rawName) : rawName;
88
+ const attributes = extractAttributes(child.openingElement);
89
+ const grandchildren = child.children
90
+ ? extractChildElements(child.children)
91
+ : [];
92
+ let hasTextContent = false;
93
+ if (child.children) {
94
+ for (const grandchild of child.children) {
95
+ if (grandchild.type === 'JSXText') {
96
+ const text = (grandchild.value ?? '').trim();
97
+ if (text.length > 0) {
98
+ hasTextContent = true;
99
+ break;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ results.push({
105
+ tagName,
106
+ attributes,
107
+ children: grandchildren,
108
+ hasTextContent,
109
+ });
110
+ }
111
+ return results;
112
+ }
113
+ /**
114
+ * Extract a ParsedElement from a JSXElement AST node.
115
+ * Returns an empty array if not an SWC element.
116
+ * Recursively extracts direct children for slot validation.
117
+ */
118
+ export function extractElementFromJSX(node) {
119
+ const jsxNode = node;
120
+ const opening = jsxNode.openingElement;
121
+ const rawName = getJsxElementName(opening.name);
122
+ if (!rawName || !isSwcJsxTag(rawName))
123
+ return [];
124
+ const tagName = resolveJsxTagName(rawName);
125
+ const attributes = extractAttributes(opening);
126
+ const children = jsxNode.children
127
+ ? extractChildElements(jsxNode.children)
128
+ : [];
129
+ let hasTextContent = false;
130
+ if (jsxNode.children) {
131
+ for (const child of jsxNode.children) {
132
+ if (child.type === 'JSXText') {
133
+ const text = (child.value ?? '').trim();
134
+ if (text.length > 0) {
135
+ hasTextContent = true;
136
+ break;
137
+ }
138
+ }
139
+ }
140
+ }
141
+ return [
142
+ {
143
+ tagName,
144
+ attributes,
145
+ children,
146
+ hasTextContent,
147
+ },
148
+ ];
149
+ }
150
+ //# sourceMappingURL=jsx-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-adapter.js","sourceRoot":"","sources":["../../src/adapters/jsx-adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ /**
13
+ * Convert a PascalCase SWC React wrapper name to its kebab-case tag name.
14
+ * e.g. "SpActionMenu" -> "sp-action-menu", "SpProgressBar" -> "sp-progress-bar"
15
+ */
16
+ export declare function pascalToKebab(name: string): string;
17
+ /**
18
+ * Convert a camelCase JSX prop name to its kebab-case HTML attribute equivalent.
19
+ * e.g. "ariaLabel" -> "aria-label", "isDecorative" -> "is-decorative"
20
+ */
21
+ export declare function camelToKebab(name: string): string;
22
+ /**
23
+ * Check whether a kebab-case tag name is a recognized SWC element.
24
+ */
25
+ export declare function isSwcTagName(tagName: string): boolean;
26
+ /**
27
+ * Check whether a JSX tag name looks like an SWC React wrapper (PascalCase starting with "Sp")
28
+ * or a custom element used directly in JSX (kebab-case starting with "sp-").
29
+ */
30
+ export declare function isSwcJsxTag(name: string): boolean;
31
+ /**
32
+ * Resolve a JSX tag name to its canonical kebab-case SWC tag name.
33
+ * Handles both PascalCase wrappers and direct custom element usage.
34
+ */
35
+ export declare function resolveJsxTagName(name: string): string;
36
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/adapters/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKjD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAItD"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Copyright 2026 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ /**
13
+ * Convert a PascalCase SWC React wrapper name to its kebab-case tag name.
14
+ * e.g. "SpActionMenu" -> "sp-action-menu", "SpProgressBar" -> "sp-progress-bar"
15
+ */
16
+ export function pascalToKebab(name) {
17
+ return name
18
+ .replace(/([A-Z])/g, (match, char, index) => index === 0 ? char.toLowerCase() : `-${char.toLowerCase()}`)
19
+ .replace(/^sp-/, 'sp-');
20
+ }
21
+ /**
22
+ * Convert a camelCase JSX prop name to its kebab-case HTML attribute equivalent.
23
+ * e.g. "ariaLabel" -> "aria-label", "isDecorative" -> "is-decorative"
24
+ */
25
+ export function camelToKebab(name) {
26
+ return name.replace(/([A-Z])/g, '-$1').toLowerCase();
27
+ }
28
+ /**
29
+ * Check whether a kebab-case tag name is a recognized SWC element.
30
+ */
31
+ export function isSwcTagName(tagName) {
32
+ return tagName.startsWith('sp-') || tagName === 'overlay-trigger';
33
+ }
34
+ /**
35
+ * Check whether a JSX tag name looks like an SWC React wrapper (PascalCase starting with "Sp")
36
+ * or a custom element used directly in JSX (kebab-case starting with "sp-").
37
+ */
38
+ export function isSwcJsxTag(name) {
39
+ if (name.startsWith('sp-') || name === 'overlay-trigger')
40
+ return true;
41
+ if (/^Sp[A-Z]/.test(name))
42
+ return true;
43
+ if (name === 'OverlayTrigger')
44
+ return true;
45
+ return false;
46
+ }
47
+ /**
48
+ * Resolve a JSX tag name to its canonical kebab-case SWC tag name.
49
+ * Handles both PascalCase wrappers and direct custom element usage.
50
+ */
51
+ export function resolveJsxTagName(name) {
52
+ if (name.startsWith('sp-') || name === 'overlay-trigger')
53
+ return name;
54
+ if (name === 'OverlayTrigger')
55
+ return 'overlay-trigger';
56
+ return pascalToKebab(name);
57
+ }
58
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/adapters/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI;SACR,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAC1C,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAC5D;SACA,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,iBAAiB,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAC;IACtE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,IAAI,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,iBAAiB;QAAE,OAAO,IAAI,CAAC;IACtE,IAAI,IAAI,KAAK,gBAAgB;QAAE,OAAO,iBAAiB,CAAC;IACxD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
@@ -30,5 +30,15 @@ export declare function createRequiredAttributesRule(descriptors: ComponentDescr
30
30
  * Creates the `valid-attribute-values` rule.
31
31
  */
32
32
  export declare function createValidAttributeValuesRule(descriptors: ComponentDescriptorMap): RuleModule;
33
+ /**
34
+ * Creates the `valid-slot-names` rule that checks children aren't placed
35
+ * into non-existent slots.
36
+ */
37
+ export declare function createValidSlotNamesRule(descriptors: ComponentDescriptorMap): RuleModule;
38
+ /**
39
+ * Creates the `valid-slot-children` rule that checks a child element's tag
40
+ * is accepted in the slot it targets.
41
+ */
42
+ export declare function createValidSlotChildrenRule(descriptors: ComponentDescriptorMap): RuleModule;
33
43
  export {};
34
44
  //# sourceMappingURL=rule-factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rule-factory.d.ts","sourceRoot":"","sources":["../../src/core/rule-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAEV,sBAAsB,EAIvB,MAAM,YAAY,CAAC;AAGpB,KAAK,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;AAmClC;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAoGZ;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAoEZ;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAyCZ;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAmDZ"}
1
+ {"version":3,"file":"rule-factory.d.ts","sourceRoot":"","sources":["../../src/core/rule-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnC,OAAO,KAAK,EAEV,sBAAsB,EAKvB,MAAM,YAAY,CAAC;AAIpB,KAAK,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;AAmClC;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAyGZ;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAyEZ;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CA8CZ;AAED;;GAEG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAwDZ;AAYD;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,sBAAsB,GAClC,UAAU,CAwDZ;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,sBAAsB,GAClC,UAAU,CA8DZ"}