@swiftwc/eslint-plugin 1.1.7-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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @swiftwc/eslint-plugin
2
2
 
3
+ ## 1.1.7-canary.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ed31ef2: - Renamed `TagNode` to `BaseNode` for clarity.
8
+
3
9
  ## 1.1.7-canary.0
4
10
 
5
11
  ### Patch Changes
@@ -1,2 +1,25 @@
1
- import { swiftwc } from '../index.js';
2
- export default swiftwc;
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;
@@ -1,18 +1,22 @@
1
- import { DIRECT_PARENT_RULES, swiftwc, validate } from '../index.js';
2
- swiftwc.rules['allowed-tags'].meta.docs.description = 'Restrict allowed HTML tags';
3
- swiftwc.rules['allowed-tags'].meta.messages.disallowedTag = 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}';
4
- swiftwc.rules['allowed-tags'].create = (context) => {
5
- return {
6
- // '*': (node) => {
7
- // if('GlimmerElementNode' !== node.type) return
8
- GlimmerElementNode(node) {
9
- // @ts-expect-error
10
- const tag = node.tag;
11
- const allowedParents = DIRECT_PARENT_RULES[tag];
12
- if (!allowedParents)
13
- return; // 👈 ignore unknown tags completely
14
- validate(tag, (item) => item.parent, allowedParents, context, node);
15
- },
16
- };
17
- };
18
- export default swiftwc;
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
+ }));
@@ -1,2 +1,25 @@
1
- import { swiftwc } from '../index.js';
2
- export default swiftwc;
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;
@@ -1,15 +1,10 @@
1
- import { DIRECT_PARENT_RULES, swiftwc, validate } from '../index.js';
2
- swiftwc.rules['allowed-tags'].meta.docs.description = 'Restrict allowed HTML tags';
3
- swiftwc.rules['allowed-tags'].meta.messages.disallowedTag = 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}';
4
- swiftwc.rules['allowed-tags'].create = (context) => {
5
- return {
6
- Tag(node) {
7
- const tag = node.name;
8
- const allowedParents = DIRECT_PARENT_RULES[tag];
9
- if (!allowedParents)
10
- return; // 👈 ignore unknown tags completely
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
+ }));
@@ -1,12 +1,10 @@
1
1
  import type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint';
2
- export type TagNode = {
3
- name: string;
2
+ export type BaseNode = {
4
3
  type: string;
5
- parent?: TagNode;
6
4
  };
7
- export declare const DIRECT_PARENT_RULES: Record<string, string[]>;
8
- export declare function validate(tag: string, getParentTag: (node: TagNode) => TagNode | undefined, allowedParents: string[], context: RuleContext<string, readonly unknown[]>, node: TagNode): void;
9
- export declare const swiftwc: {
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[]>>): RuleListener;
27
+ create: (context: Readonly<RuleContext<string, readonly unknown[]>>) => RuleListener;
30
28
  };
31
29
  };
32
30
  };
@@ -1,5 +1,5 @@
1
1
  import { readFileSync } from 'fs';
2
- export const DIRECT_PARENT_RULES = {
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 parentTag = getParentTag(node);
13
- if (parentTag?.type === node.type && allowedParents.includes(parentTag.name))
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
- // @ts-expect-error
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 const swiftwc = {
37
- meta: {
38
- name,
39
- version,
40
- namespace: 'swiftwc',
41
- },
42
- configs: {},
43
- rules: {
44
- 'allowed-tags': {
45
- meta: {
46
- type: 'problem',
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
- Object.assign(swiftwc.configs, {
122
- recommended: [
123
- {
124
- plugins: {
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.7-canary.0",
3
+ "version": "1.1.7-canary.1",
4
4
  "description": "Rules enforcing best practices while using SwiftWC.",
5
5
  "keywords": [
6
6
  "eslint",
package/src/emberjs/en.ts CHANGED
@@ -1,24 +1,24 @@
1
- import type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint'
2
- import { DIRECT_PARENT_RULES, swiftwc, validate, type TagNode } from '../index.js'
1
+ import { createPlugin, getAllowedParents, validate } from '../index.js'
3
2
 
4
- swiftwc.rules['allowed-tags'].meta.docs.description = 'Restrict allowed HTML tags'
3
+ type GlimmerNode = { type: string; tag?: string; parent?: GlimmerNode }
5
4
 
6
- swiftwc.rules['allowed-tags'].meta.messages.disallowedTag = 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}'
7
-
8
- swiftwc.rules['allowed-tags'].create = (context: Readonly<RuleContext<string, readonly unknown[]>>): RuleListener => {
9
- return {
10
- // '*': (node) => {
11
- // if('GlimmerElementNode' !== node.type) return
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 swiftwc
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 type { RuleContext, RuleListener } from '@typescript-eslint/utils/ts-eslint'
2
- import { DIRECT_PARENT_RULES, swiftwc, validate, type TagNode } from '../index.js'
1
+ import { createPlugin, getAllowedParents, validate } from '../index.js'
3
2
 
4
- swiftwc.rules['allowed-tags'].meta.docs.description = 'Restrict allowed HTML tags'
3
+ type HtmlNode = { type: 'Tag'; name: string; parent?: { type: string; name?: string } }
5
4
 
6
- swiftwc.rules['allowed-tags'].meta.messages.disallowedTag = 'Tag <{{tag}}> is only allowed inside any of: {{allowed}}'
5
+ const getParentName = (n: HtmlNode) => (n.parent?.type === 'Tag' ? n.parent.name : undefined)
7
6
 
8
- swiftwc.rules['allowed-tags'].create = (context: Readonly<RuleContext<string, readonly unknown[]>>): RuleListener => {
9
- return {
10
- Tag(node: TagNode) {
11
- const tag = node.name
7
+ export default createPlugin((context) => ({
8
+ Tag(node: HtmlNode) {
9
+ const allowedParents = getAllowedParents(node.name)
10
+ if (!allowedParents) return
12
11
 
13
- const allowedParents = DIRECT_PARENT_RULES[tag]
14
- if (!allowedParents) return // 👈 ignore unknown tags completely
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 TagNode = {
5
- name: string
6
- type: string
7
- parent?: TagNode
8
- }
5
+ export type BaseNode = { type: string }
9
6
 
10
- export const DIRECT_PARENT_RULES: Record<string, string[]> = {
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 validate(
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: TagNode) => TagNode | undefined,
29
+ getParentTag: (node: N) => string | undefined,
29
30
  allowedParents: string[],
30
31
  context: RuleContext<string, readonly unknown[]>,
31
- node: TagNode
32
+ node: N
32
33
  ) {
33
- const parentTag = getParentTag(node)
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
- // @ts-expect-error
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 const swiftwc = {
63
- meta: {
64
- name,
65
- version,
66
- namespace: 'swiftwc',
67
- },
68
- configs: {},
69
- rules: {
70
- 'allowed-tags': {
71
- meta: {
72
- type: 'problem',
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
- // languages: ['html/html'],
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(swiftwc.configs, {
156
- recommended: [
157
- {
158
- plugins: {
159
- swiftwc,
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
+ }