Skip to content

Commit c3dd876

Browse files
committed
implement
1 parent 66e89af commit c3dd876

File tree

9 files changed

+406
-97
lines changed

9 files changed

+406
-97
lines changed

packages/eslint-plugin/lib/rules/use-standard-html.js

Lines changed: 0 additions & 86 deletions
This file was deleted.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/**
2+
* @typedef { import("html-standard").ElementSpec } ElementSpec
3+
* @typedef { import("html-standard").ContentModel } ContentModel
4+
* @typedef { import("@html-eslint/types").AnyHTMLNode} AnyHTMLNode
5+
* @typedef { import("@html-eslint/types").Tag} Tag
6+
* @typedef { import("@html-eslint/types").Text} Text
7+
*
8+
* @typedef { import("./use-standard-html").Option} Option
9+
* @typedef { import("../../types").Context<[Option]> } Context
10+
*
11+
* @typedef {Object} State
12+
* @property {number} contentModelIndex
13+
* @property {ContentModel[] | null} contentModels
14+
* @property {number} childIndex
15+
* @property {AnyHTMLNode[]} children
16+
*/
17+
18+
const { shouldIgnoreChild, getNodeName } = require("./helpers");
19+
20+
const EXIT = false;
21+
const CONTINUE = true;
22+
23+
const MESSAGE_IDS = {
24+
REQUIRED: "required",
25+
NOT_ALLOWED: "notAllowed",
26+
};
27+
28+
/**
29+
* @param {State} state
30+
* @returns {AnyHTMLNode | null}
31+
*/
32+
function getChild(state) {
33+
return state.children[state.childIndex];
34+
}
35+
36+
/**
37+
* @param {State} state
38+
* @returns {ContentModel | null}
39+
*/
40+
function getContentModel(state) {
41+
if (!state.contentModels) {
42+
return null;
43+
}
44+
return state.contentModels[state.contentModelIndex] || null;
45+
}
46+
47+
/**
48+
* @param {Context} context
49+
* @param {ElementSpec} spec
50+
* @param {AnyHTMLNode} node
51+
* @param {AnyHTMLNode[]} children
52+
*/
53+
function checkContentModel(context, spec, node, children) {
54+
/**
55+
* @type {State}
56+
*/
57+
const state = {
58+
contentModels: spec.contents,
59+
contentModelIndex: 0,
60+
childIndex: 0,
61+
children,
62+
};
63+
let result = CONTINUE;
64+
while (result && state.contentModels && !!getContentModel(state)) {
65+
const contentModel = getContentModel(state);
66+
if (!contentModel) {
67+
return;
68+
}
69+
switch (contentModel.type) {
70+
case "required": {
71+
result = required(contentModel, context, state, node);
72+
break;
73+
}
74+
case "zeroOrMore": {
75+
result = zeroOrMore(contentModel, state);
76+
break;
77+
}
78+
case "oneOrMore": {
79+
result = oneOrMore(contentModel, context, state, node);
80+
break;
81+
}
82+
case "optional": {
83+
result = optional(state);
84+
break;
85+
}
86+
case "either": {
87+
break;
88+
}
89+
default: {
90+
result = EXIT;
91+
}
92+
}
93+
}
94+
const remain = getChild(state);
95+
const contentModel = getContentModel(state);
96+
if (remain && !contentModel) {
97+
context.report({
98+
node: remain,
99+
messageId: MESSAGE_IDS.NOT_ALLOWED,
100+
});
101+
}
102+
}
103+
104+
/**
105+
* @param {ContentModel & {type: "required"}} model
106+
* @param {Context} context
107+
* @param {State} state
108+
* @param {AnyHTMLNode} node
109+
* @returns {boolean}
110+
*/
111+
function required(model, context, state, node) {
112+
let child = getChild(state);
113+
if (!child) {
114+
context.report({
115+
node,
116+
messageId: MESSAGE_IDS.REQUIRED,
117+
});
118+
return EXIT;
119+
}
120+
if (shouldIgnoreChild(child)) {
121+
state.childIndex++;
122+
}
123+
const name = getNodeName(child);
124+
if (model.contents.has(name)) {
125+
state.childIndex++;
126+
state.contentModelIndex++;
127+
return CONTINUE;
128+
}
129+
context.report({
130+
node,
131+
messageId: MESSAGE_IDS.REQUIRED,
132+
});
133+
return EXIT;
134+
}
135+
136+
/**
137+
* @param {ContentModel & {type: "zeroOrMore"}} model
138+
* @param {State} state
139+
* @returns {boolean}
140+
*/
141+
function zeroOrMore(model, state) {
142+
let child = getChild(state);
143+
if (!child) {
144+
state.childIndex++;
145+
state.contentModelIndex++;
146+
return CONTINUE;
147+
}
148+
while ((child = getChild(state))) {
149+
if (shouldIgnoreChild(child)) {
150+
state.childIndex++;
151+
continue;
152+
}
153+
const name = getNodeName(child);
154+
if (model.contents.has(name)) {
155+
state.childIndex++;
156+
} else {
157+
break;
158+
}
159+
}
160+
161+
state.contentModelIndex++;
162+
return CONTINUE;
163+
}
164+
165+
/**
166+
* @param {ContentModel & {type: "oneOrMore"}} model
167+
* @param {Context} context
168+
* @param {State} state
169+
* @param {AnyHTMLNode} node
170+
* @returns {boolean}
171+
*/
172+
function oneOrMore(model, context, state, node) {
173+
let child = getChild(state);
174+
if (!child) {
175+
state.childIndex++;
176+
state.contentModelIndex++;
177+
return CONTINUE;
178+
}
179+
let count = 0;
180+
while ((child = getChild(state))) {
181+
if (shouldIgnoreChild(child)) {
182+
state.childIndex++;
183+
continue;
184+
}
185+
const name = getNodeName(child);
186+
if (model.contents.has(name)) {
187+
count++;
188+
state.childIndex++;
189+
} else {
190+
break;
191+
}
192+
}
193+
194+
if (count <= 0) {
195+
context.report({
196+
node,
197+
messageId: MESSAGE_IDS.REQUIRED,
198+
});
199+
return EXIT;
200+
}
201+
202+
state.contentModelIndex++;
203+
return CONTINUE;
204+
}
205+
206+
/**
207+
* @param {State} state
208+
* @returns {boolean}
209+
*/
210+
function optional(state) {
211+
let child = getChild(state);
212+
if (!child) {
213+
state.childIndex++;
214+
state.contentModelIndex++;
215+
return CONTINUE;
216+
}
217+
state.childIndex++;
218+
state.contentModelIndex++;
219+
return CONTINUE;
220+
}
221+
222+
module.exports = {
223+
MESSAGE_IDS,
224+
checkContentModel,
225+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @typedef { import("@html-eslint/types").AnyHTMLNode} AnyHTMLNode
3+
*/
4+
5+
const {
6+
isWhitespacesText,
7+
isComment,
8+
isTag,
9+
isText,
10+
isScript,
11+
isStyle,
12+
} = require("../utils/node");
13+
14+
/**
15+
* @param {AnyHTMLNode} child
16+
*/
17+
function shouldIgnoreChild(child) {
18+
return isWhitespacesText(child) || isComment(child);
19+
}
20+
21+
/**
22+
* @param {AnyHTMLNode} node
23+
* @returns {string}
24+
*/
25+
function getNodeName(node) {
26+
if (isTag(node)) {
27+
return node.name;
28+
}
29+
if (isText(node)) {
30+
return "#text";
31+
}
32+
if (isScript(node)) {
33+
return "script";
34+
}
35+
if (isStyle(node)) {
36+
return "style";
37+
}
38+
if (isComment(node)) {
39+
return "#comment";
40+
}
41+
return "#unknown";
42+
}
43+
44+
module.exports = {
45+
shouldIgnoreChild,
46+
getNodeName,
47+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const useStandardHtml = require("./use-standard-html");
2+
module.exports = useStandardHtml;

0 commit comments

Comments
 (0)