@swiftwc/eslint-plugin 1.1.6-canary.0 → 1.1.7-canary.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/CHANGELOG.md +15 -0
- package/generated/emberjs/en.d.ts +25 -2
- package/generated/emberjs/en.js +22 -18
- package/generated/html/en.d.ts +25 -2
- package/generated/html/en.js +10 -15
- package/generated/index.d.ts +5 -7
- package/generated/index.js +25 -109
- package/package.json +5 -5
- package/src/emberjs/en.ts +19 -19
- package/src/html/en.ts +10 -17
- package/src/index.ts +32 -127
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @swiftwc/eslint-plugin
|
|
2
|
+
|
|
3
|
+
## 1.1.7-canary.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ed31ef2: - Renamed `TagNode` to `BaseNode` for clarity.
|
|
8
|
+
|
|
9
|
+
## 1.1.7-canary.0
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- e5f443f: adopted changesets
|
|
14
|
+
- Updated dependencies [76e1cbb]
|
|
15
|
+
- @swiftwc/ui@1.0.1-canary.0
|
|
@@ -1,2 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
name: any;
|
|
4
|
+
version: any;
|
|
5
|
+
namespace: string;
|
|
6
|
+
};
|
|
7
|
+
configs: {};
|
|
8
|
+
rules: {
|
|
9
|
+
'allowed-tags': {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem";
|
|
12
|
+
docs: {
|
|
13
|
+
description: string;
|
|
14
|
+
url: string;
|
|
15
|
+
};
|
|
16
|
+
schema: never[];
|
|
17
|
+
messages: {
|
|
18
|
+
disallowedTag: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
create: (context: Readonly<import("@typescript-eslint/utils/ts-eslint").RuleContext<string, readonly unknown[]>>) => import("@typescript-eslint/utils/ts-eslint").RuleListener;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export default _default;
|
package/generated/emberjs/en.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { createPlugin, getAllowedParents, validate } from '../index.js';
|
|
2
|
+
/** Nearest real element ancestor; skips {{#let}}/{{#if}}/{{#each}} blocks, mustaches, text. */
|
|
3
|
+
/** This still accepts content-view, div and scroll-view, and rejects Foo, :header, @arg, this.foo and foo.bar. */
|
|
4
|
+
function getParentName(n) {
|
|
5
|
+
for (let p = n.parent; p; p = p.parent) {
|
|
6
|
+
if (p.type === 'GlimmerElementNode' && /^[a-z][\w-]*$/.test(p.tag))
|
|
7
|
+
return p.tag;
|
|
8
|
+
if (p.type === 'GlimmerTemplate')
|
|
9
|
+
return 'template';
|
|
10
|
+
}
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
export default createPlugin((context) => ({
|
|
14
|
+
// '*': (node) => {
|
|
15
|
+
// if('GlimmerElementNode' !== node.type) return
|
|
16
|
+
GlimmerElementNode(node) {
|
|
17
|
+
const allowedParents = getAllowedParents(node.tag);
|
|
18
|
+
if (!allowedParents)
|
|
19
|
+
return;
|
|
20
|
+
validate(node.tag, getParentName, allowedParents, context, node);
|
|
21
|
+
},
|
|
22
|
+
}));
|
package/generated/html/en.d.ts
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
name: any;
|
|
4
|
+
version: any;
|
|
5
|
+
namespace: string;
|
|
6
|
+
};
|
|
7
|
+
configs: {};
|
|
8
|
+
rules: {
|
|
9
|
+
'allowed-tags': {
|
|
10
|
+
meta: {
|
|
11
|
+
type: "problem";
|
|
12
|
+
docs: {
|
|
13
|
+
description: string;
|
|
14
|
+
url: string;
|
|
15
|
+
};
|
|
16
|
+
schema: never[];
|
|
17
|
+
messages: {
|
|
18
|
+
disallowedTag: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
create: (context: Readonly<import("@typescript-eslint/utils/ts-eslint").RuleContext<string, readonly unknown[]>>) => import("@typescript-eslint/utils/ts-eslint").RuleListener;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export default _default;
|
package/generated/html/en.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
validate(tag, (item) => item.parent, allowedParents, context, node);
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
export default swiftwc;
|
|
1
|
+
import { createPlugin, getAllowedParents, validate } from '../index.js';
|
|
2
|
+
const getParentName = (n) => (n.parent?.type === 'Tag' ? n.parent.name : undefined);
|
|
3
|
+
export default createPlugin((context) => ({
|
|
4
|
+
Tag(node) {
|
|
5
|
+
const allowedParents = getAllowedParents(node.name);
|
|
6
|
+
if (!allowedParents)
|
|
7
|
+
return;
|
|
8
|
+
validate(node.name, getParentName, allowedParents, context, node);
|
|
9
|
+
},
|
|
10
|
+
}));
|
package/generated/index.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint';
|
|
2
|
-
export type
|
|
3
|
-
name: string;
|
|
2
|
+
export type BaseNode = {
|
|
4
3
|
type: string;
|
|
5
|
-
parent?: TagNode;
|
|
6
4
|
};
|
|
7
|
-
export declare
|
|
8
|
-
export declare function validate(tag: string, getParentTag: (node:
|
|
9
|
-
export declare
|
|
5
|
+
export declare function getAllowedParents(tag: string): string[] | undefined;
|
|
6
|
+
export declare function validate<N extends BaseNode>(tag: string, getParentTag: (node: N) => string | undefined, allowedParents: string[], context: RuleContext<string, readonly unknown[]>, node: N): void;
|
|
7
|
+
export declare function createPlugin(create: (context: Readonly<RuleContext<string, readonly unknown[]>>) => RuleListener): {
|
|
10
8
|
meta: {
|
|
11
9
|
name: any;
|
|
12
10
|
version: any;
|
|
@@ -26,7 +24,7 @@ export declare const swiftwc: {
|
|
|
26
24
|
disallowedTag: string;
|
|
27
25
|
};
|
|
28
26
|
};
|
|
29
|
-
create(context: Readonly<RuleContext<string, readonly unknown[]>>)
|
|
27
|
+
create: (context: Readonly<RuleContext<string, readonly unknown[]>>) => RuleListener;
|
|
30
28
|
};
|
|
31
29
|
};
|
|
32
30
|
};
|
package/generated/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
-
|
|
2
|
+
const DIRECT_PARENT_RULES = {
|
|
3
3
|
'v-keyboard': ['template', 'body'],
|
|
4
4
|
'scroll-view': ['template', 'content-view', 'dialog', 'navigation-stack', 'navigation-split-view', 'detail-placeholder'],
|
|
5
5
|
dialog: ['template', 'tab-view', 'navigation-split-view'],
|
|
@@ -8,23 +8,15 @@ export const DIRECT_PARENT_RULES = {
|
|
|
8
8
|
'sidebar-toggle': ['template', 'tab-view', 'form', 'navigation-split-view', 'tool-bar'],
|
|
9
9
|
'content-view': ['template', 'dialog', 'content-view', 'navigation-stack', 'navigation-split-view'],
|
|
10
10
|
};
|
|
11
|
+
export function getAllowedParents(tag) {
|
|
12
|
+
return Object.hasOwn(DIRECT_PARENT_RULES, tag) ? DIRECT_PARENT_RULES[tag] : undefined;
|
|
13
|
+
}
|
|
11
14
|
export function validate(tag, getParentTag, allowedParents, context, node) {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
15
|
+
const parent = getParentTag(node);
|
|
16
|
+
if (parent && allowedParents.includes(parent))
|
|
14
17
|
return;
|
|
15
|
-
// let parentTag = getParentTag(node),
|
|
16
|
-
// isValid = false
|
|
17
|
-
// while (parentTag) {
|
|
18
|
-
// if (parentTag.type === node.type && allowedParents.includes(parentTag.name)) {
|
|
19
|
-
// isValid = true
|
|
20
|
-
// break
|
|
21
|
-
// }
|
|
22
|
-
// parentTag = getParentTag(parentTag)
|
|
23
|
-
// }
|
|
24
|
-
// if (isValid) return
|
|
25
18
|
context.report({
|
|
26
|
-
|
|
27
|
-
node,
|
|
19
|
+
node: node,
|
|
28
20
|
messageId: 'disallowedTag',
|
|
29
21
|
data: {
|
|
30
22
|
tag,
|
|
@@ -33,100 +25,24 @@ export function validate(tag, getParentTag, allowedParents, context, node) {
|
|
|
33
25
|
});
|
|
34
26
|
}
|
|
35
27
|
const { name, version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
name,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
docs: {
|
|
48
|
-
description: 'Restrict allowed HTML tags',
|
|
49
|
-
url: `https://github.com/swiftwc/ui`,
|
|
50
|
-
},
|
|
51
|
-
// languages: ['html/html'],
|
|
52
|
-
schema: [],
|
|
53
|
-
// schema: [
|
|
54
|
-
// {
|
|
55
|
-
// type: 'object',
|
|
56
|
-
// properties: {
|
|
57
|
-
// allowed: {
|
|
58
|
-
// type: 'array',
|
|
59
|
-
// items: { type: 'string' },
|
|
60
|
-
// },
|
|
61
|
-
// },
|
|
62
|
-
// additionalProperties: false,
|
|
63
|
-
// },
|
|
64
|
-
// ],
|
|
65
|
-
messages: {
|
|
66
|
-
disallowedTag: 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}',
|
|
28
|
+
export function createPlugin(create) {
|
|
29
|
+
const plugin = {
|
|
30
|
+
meta: { name, version, namespace: 'swiftwc' },
|
|
31
|
+
configs: {},
|
|
32
|
+
rules: {
|
|
33
|
+
'allowed-tags': {
|
|
34
|
+
meta: {
|
|
35
|
+
type: 'problem',
|
|
36
|
+
docs: { description: 'Restrict allowed HTML tags', url: 'https://github.com/swiftwc/ui' },
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: { disallowedTag: 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}' },
|
|
67
39
|
},
|
|
68
|
-
|
|
69
|
-
// create(context) {
|
|
70
|
-
// const options = context.options?.[0] || {},
|
|
71
|
-
// allowed = new Set(options.allowed || [...DEFAULT_ALLOWED])
|
|
72
|
-
// return {
|
|
73
|
-
// Tag(node) {
|
|
74
|
-
// const tag = node.name
|
|
75
|
-
// if (!allowed.has(tag)) {
|
|
76
|
-
// context.report({
|
|
77
|
-
// node,
|
|
78
|
-
// messageId: 'disallowedTag',
|
|
79
|
-
// data: {
|
|
80
|
-
// tag,
|
|
81
|
-
// allowed: [...allowed].join(', '),
|
|
82
|
-
// },
|
|
83
|
-
// })
|
|
84
|
-
// }
|
|
85
|
-
// },
|
|
86
|
-
// }
|
|
87
|
-
// },
|
|
88
|
-
create(context) {
|
|
89
|
-
return {
|
|
90
|
-
Tag(node) {
|
|
91
|
-
const tag = node.name;
|
|
92
|
-
const allowedParents = DIRECT_PARENT_RULES[tag];
|
|
93
|
-
if (!allowedParents)
|
|
94
|
-
return; // 👈 ignore unknown tags completely
|
|
95
|
-
validate(tag, (item) => item.parent, allowedParents, context, node);
|
|
96
|
-
// let parent = node.parent
|
|
97
|
-
// let valid = false
|
|
98
|
-
// while (parent) {
|
|
99
|
-
// if (parent.type === 'Tag' && allowedParents.includes(parent.name)) {
|
|
100
|
-
// valid = true
|
|
101
|
-
// break
|
|
102
|
-
// }
|
|
103
|
-
// parent = parent.parent
|
|
104
|
-
// }
|
|
105
|
-
// if (!valid) {
|
|
106
|
-
// context.report({
|
|
107
|
-
// node,
|
|
108
|
-
// messageId: 'disallowedTag',
|
|
109
|
-
// data: {
|
|
110
|
-
// tag,
|
|
111
|
-
// allowed: allowedParents.join(', '),
|
|
112
|
-
// },
|
|
113
|
-
// })
|
|
114
|
-
// }
|
|
115
|
-
},
|
|
116
|
-
};
|
|
40
|
+
create,
|
|
117
41
|
},
|
|
118
42
|
},
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
swiftwc,
|
|
126
|
-
},
|
|
127
|
-
rules: {
|
|
128
|
-
'swiftwc/allowed-tags': 'error', //['error', { allowed: ['div', 'span', 'my-button', 'my-card'] }],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
});
|
|
43
|
+
};
|
|
44
|
+
Object.assign(plugin.configs, {
|
|
45
|
+
recommended: [{ plugins: { swiftwc: plugin }, rules: { 'swiftwc/allowed-tags': 'error' } }],
|
|
46
|
+
});
|
|
47
|
+
return plugin;
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftwc/eslint-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7-canary.1",
|
|
4
4
|
"description": "Rules enforcing best practices while using SwiftWC.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@swiftwc/ui": "
|
|
25
|
+
"@swiftwc/ui": "1.0.1-canary.0",
|
|
26
26
|
"eslint": ">=10.0.0"
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"./emberjs/en": "./generated/emberjs/en.js"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"
|
|
33
|
+
"lint:types": "tsc --noEmit",
|
|
34
34
|
"prebuild": "rm -rf generated",
|
|
35
35
|
"build": "npm run build:ts",
|
|
36
36
|
"build:ts": "tsc",
|
|
37
37
|
"build:ts:watch": "npm run prebuild && tsc --watch",
|
|
38
38
|
"prepublishOnly": "npm run build",
|
|
39
|
-
"
|
|
40
|
-
"format
|
|
39
|
+
"lint": "prettier . --check",
|
|
40
|
+
"format": "prettier . --write"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@typescript-eslint/utils": "^8.59.2"
|
package/src/emberjs/en.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DIRECT_PARENT_RULES, swiftwc, validate, type TagNode } from '../index.js'
|
|
1
|
+
import { createPlugin, getAllowedParents, validate } from '../index.js'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type GlimmerNode = { type: string; tag?: string; parent?: GlimmerNode }
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
GlimmerElementNode(node: TagNode) {
|
|
13
|
-
// @ts-expect-error
|
|
14
|
-
const tag = node.tag
|
|
15
|
-
|
|
16
|
-
const allowedParents = DIRECT_PARENT_RULES[tag]
|
|
17
|
-
if (!allowedParents) return // 👈 ignore unknown tags completely
|
|
18
|
-
|
|
19
|
-
validate(tag, (item) => item.parent, allowedParents, context, node)
|
|
20
|
-
},
|
|
5
|
+
/** Nearest real element ancestor; skips {{#let}}/{{#if}}/{{#each}} blocks, mustaches, text. */
|
|
6
|
+
/** This still accepts content-view, div and scroll-view, and rejects Foo, :header, @arg, this.foo and foo.bar. */
|
|
7
|
+
function getParentName(n: GlimmerNode): string | undefined {
|
|
8
|
+
for (let p = n.parent; p; p = p.parent) {
|
|
9
|
+
if (p.type === 'GlimmerElementNode' && /^[a-z][\w-]*$/.test(p.tag!)) return p.tag
|
|
10
|
+
if (p.type === 'GlimmerTemplate') return 'template'
|
|
21
11
|
}
|
|
12
|
+
return undefined
|
|
22
13
|
}
|
|
23
14
|
|
|
24
|
-
export default
|
|
15
|
+
export default createPlugin((context) => ({
|
|
16
|
+
// '*': (node) => {
|
|
17
|
+
// if('GlimmerElementNode' !== node.type) return
|
|
18
|
+
GlimmerElementNode(node: GlimmerNode) {
|
|
19
|
+
const allowedParents = getAllowedParents(node.tag!)
|
|
20
|
+
if (!allowedParents) return
|
|
21
|
+
|
|
22
|
+
validate(node.tag!, getParentName, allowedParents, context, node)
|
|
23
|
+
},
|
|
24
|
+
}))
|
package/src/html/en.ts
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { DIRECT_PARENT_RULES, swiftwc, validate, type TagNode } from '../index.js'
|
|
1
|
+
import { createPlugin, getAllowedParents, validate } from '../index.js'
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
type HtmlNode = { type: 'Tag'; name: string; parent?: { type: string; name?: string } }
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const getParentName = (n: HtmlNode) => (n.parent?.type === 'Tag' ? n.parent.name : undefined)
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
export default createPlugin((context) => ({
|
|
8
|
+
Tag(node: HtmlNode) {
|
|
9
|
+
const allowedParents = getAllowedParents(node.name)
|
|
10
|
+
if (!allowedParents) return
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
validate(tag, (item) => item.parent, allowedParents, context, node)
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default swiftwc
|
|
12
|
+
validate(node.name, getParentName, allowedParents, context, node)
|
|
13
|
+
},
|
|
14
|
+
}))
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import type { TSESTree } from '@typescript-eslint/utils'
|
|
1
2
|
import type { Linter, RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint'
|
|
2
3
|
import { readFileSync } from 'fs'
|
|
3
4
|
|
|
4
|
-
export type
|
|
5
|
-
name: string
|
|
6
|
-
type: string
|
|
7
|
-
parent?: TagNode
|
|
8
|
-
}
|
|
5
|
+
export type BaseNode = { type: string }
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
const DIRECT_PARENT_RULES: Record<string, string[]> = {
|
|
11
8
|
'v-keyboard': ['template', 'body'],
|
|
12
9
|
|
|
13
10
|
'scroll-view': ['template', 'content-view', 'dialog', 'navigation-stack', 'navigation-split-view', 'detail-placeholder'],
|
|
@@ -23,32 +20,22 @@ export const DIRECT_PARENT_RULES: Record<string, string[]> = {
|
|
|
23
20
|
'content-view': ['template', 'dialog', 'content-view', 'navigation-stack', 'navigation-split-view'],
|
|
24
21
|
}
|
|
25
22
|
|
|
26
|
-
export function
|
|
23
|
+
export function getAllowedParents(tag: string): string[] | undefined {
|
|
24
|
+
return Object.hasOwn(DIRECT_PARENT_RULES, tag) ? DIRECT_PARENT_RULES[tag] : undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function validate<N extends BaseNode>(
|
|
27
28
|
tag: string,
|
|
28
|
-
getParentTag: (node:
|
|
29
|
+
getParentTag: (node: N) => string | undefined,
|
|
29
30
|
allowedParents: string[],
|
|
30
31
|
context: RuleContext<string, readonly unknown[]>,
|
|
31
|
-
node:
|
|
32
|
+
node: N
|
|
32
33
|
) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
if (parentTag?.type === node.type && allowedParents.includes(parentTag.name)) return
|
|
36
|
-
|
|
37
|
-
// let parentTag = getParentTag(node),
|
|
38
|
-
// isValid = false
|
|
39
|
-
// while (parentTag) {
|
|
40
|
-
// if (parentTag.type === node.type && allowedParents.includes(parentTag.name)) {
|
|
41
|
-
// isValid = true
|
|
42
|
-
// break
|
|
43
|
-
// }
|
|
44
|
-
// parentTag = getParentTag(parentTag)
|
|
45
|
-
// }
|
|
46
|
-
|
|
47
|
-
// if (isValid) return
|
|
34
|
+
const parent = getParentTag(node)
|
|
35
|
+
if (parent && allowedParents.includes(parent)) return
|
|
48
36
|
|
|
49
37
|
context.report({
|
|
50
|
-
|
|
51
|
-
node,
|
|
38
|
+
node: node as unknown as TSESTree.Node,
|
|
52
39
|
messageId: 'disallowedTag',
|
|
53
40
|
data: {
|
|
54
41
|
tag,
|
|
@@ -59,108 +46,26 @@ export function validate(
|
|
|
59
46
|
|
|
60
47
|
const { name, version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
|
|
61
48
|
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
name,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
docs: {
|
|
74
|
-
description: 'Restrict allowed HTML tags',
|
|
75
|
-
url: `https://github.com/swiftwc/ui`,
|
|
49
|
+
export function createPlugin(create: (context: Readonly<RuleContext<string, readonly unknown[]>>) => RuleListener) {
|
|
50
|
+
const plugin = {
|
|
51
|
+
meta: { name, version, namespace: 'swiftwc' },
|
|
52
|
+
configs: {},
|
|
53
|
+
rules: {
|
|
54
|
+
'allowed-tags': {
|
|
55
|
+
meta: {
|
|
56
|
+
type: 'problem',
|
|
57
|
+
docs: { description: 'Restrict allowed HTML tags', url: 'https://github.com/swiftwc/ui' },
|
|
58
|
+
schema: [],
|
|
59
|
+
messages: { disallowedTag: 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}' },
|
|
76
60
|
},
|
|
77
|
-
|
|
78
|
-
schema: [],
|
|
79
|
-
// schema: [
|
|
80
|
-
// {
|
|
81
|
-
// type: 'object',
|
|
82
|
-
// properties: {
|
|
83
|
-
// allowed: {
|
|
84
|
-
// type: 'array',
|
|
85
|
-
// items: { type: 'string' },
|
|
86
|
-
// },
|
|
87
|
-
// },
|
|
88
|
-
// additionalProperties: false,
|
|
89
|
-
// },
|
|
90
|
-
// ],
|
|
91
|
-
messages: {
|
|
92
|
-
disallowedTag: 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}',
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
// create(context) {
|
|
97
|
-
// const options = context.options?.[0] || {},
|
|
98
|
-
// allowed = new Set(options.allowed || [...DEFAULT_ALLOWED])
|
|
99
|
-
|
|
100
|
-
// return {
|
|
101
|
-
// Tag(node) {
|
|
102
|
-
// const tag = node.name
|
|
103
|
-
// if (!allowed.has(tag)) {
|
|
104
|
-
// context.report({
|
|
105
|
-
// node,
|
|
106
|
-
// messageId: 'disallowedTag',
|
|
107
|
-
// data: {
|
|
108
|
-
// tag,
|
|
109
|
-
// allowed: [...allowed].join(', '),
|
|
110
|
-
// },
|
|
111
|
-
// })
|
|
112
|
-
// }
|
|
113
|
-
// },
|
|
114
|
-
// }
|
|
115
|
-
// },
|
|
116
|
-
|
|
117
|
-
create(context: Readonly<RuleContext<string, readonly unknown[]>>): RuleListener {
|
|
118
|
-
return {
|
|
119
|
-
Tag(node: TagNode) {
|
|
120
|
-
const tag = node.name
|
|
121
|
-
|
|
122
|
-
const allowedParents = DIRECT_PARENT_RULES[tag]
|
|
123
|
-
if (!allowedParents) return // 👈 ignore unknown tags completely
|
|
124
|
-
|
|
125
|
-
validate(tag, (item) => item.parent, allowedParents, context, node)
|
|
126
|
-
|
|
127
|
-
// let parent = node.parent
|
|
128
|
-
// let valid = false
|
|
129
|
-
|
|
130
|
-
// while (parent) {
|
|
131
|
-
// if (parent.type === 'Tag' && allowedParents.includes(parent.name)) {
|
|
132
|
-
// valid = true
|
|
133
|
-
// break
|
|
134
|
-
// }
|
|
135
|
-
// parent = parent.parent
|
|
136
|
-
// }
|
|
137
|
-
|
|
138
|
-
// if (!valid) {
|
|
139
|
-
// context.report({
|
|
140
|
-
// node,
|
|
141
|
-
// messageId: 'disallowedTag',
|
|
142
|
-
// data: {
|
|
143
|
-
// tag,
|
|
144
|
-
// allowed: allowedParents.join(', '),
|
|
145
|
-
// },
|
|
146
|
-
// })
|
|
147
|
-
// }
|
|
148
|
-
},
|
|
149
|
-
}
|
|
61
|
+
create,
|
|
150
62
|
},
|
|
151
63
|
},
|
|
152
|
-
}
|
|
153
|
-
} satisfies Linter.Plugin
|
|
64
|
+
} satisfies Linter.Plugin
|
|
154
65
|
|
|
155
|
-
Object.assign(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
rules: {
|
|
162
|
-
'swiftwc/allowed-tags': 'error', //['error', { allowed: ['div', 'span', 'my-button', 'my-card'] }],
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
})
|
|
66
|
+
Object.assign(plugin.configs, {
|
|
67
|
+
recommended: [{ plugins: { swiftwc: plugin }, rules: { 'swiftwc/allowed-tags': 'error' } }],
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
return plugin
|
|
71
|
+
}
|