Skip to content

Commit 81fd18f

Browse files
committed
implement indent
1 parent fef70d7 commit 81fd18f

File tree

3 files changed

+436
-1
lines changed

3 files changed

+436
-1
lines changed

lib/rules/template-indent.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
const { builtinRules } = require('eslint/use-at-your-own-risk');
2+
3+
const baseRule = builtinRules.get('indent');
4+
const IGNORED_ELEMENTS = new Set(['pre', 'script', 'style', 'textarea']);
5+
6+
/** @type {import('eslint').Rule.RuleModule} */
7+
module.exports = {
8+
ERROR_MESSAGE: baseRule.meta.messages.wrongIndentation,
9+
name: 'indent',
10+
meta: {
11+
type: 'layout',
12+
docs: {
13+
description: 'enforce consistent indentation',
14+
// too opinionated to be recommended
15+
extendsBaseRule: true,
16+
recommended: true,
17+
category: 'Ember Octane',
18+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-indent.md',
19+
},
20+
fixable: 'whitespace',
21+
hasSuggestions: baseRule.meta.hasSuggestions,
22+
schema: baseRule.meta.schema,
23+
messages: baseRule.meta.messages,
24+
},
25+
26+
create: (context) => {
27+
const rules = baseRule.create(context);
28+
const sourceCode = context.sourceCode;
29+
30+
function JSXElement(node) {
31+
let closingElement;
32+
let openingElement;
33+
if (node.type === 'GlimmerElementNode') {
34+
const tokens = sourceCode.getTokens(node);
35+
const openEnd = tokens.find(t => t.value === '>');
36+
const closeStart = tokens.findLast(t => t.value === '<');
37+
if (!node.selfClosing) {
38+
closingElement = {
39+
type: 'JSXClosingElement',
40+
parent: node,
41+
range: [closeStart.range[0], node.range[1]],
42+
loc: {
43+
start: Object.assign({}, node.loc.start),
44+
end: Object.assign({}, node.loc.end),
45+
},
46+
};
47+
closingElement.loc.start = sourceCode.getLocFromIndex(closeStart.range[0]);
48+
closingElement.name = { ...closingElement, type: 'JSXIdentifier' };
49+
closingElement.name.range = [
50+
closingElement.name.range[0] + 1,
51+
closingElement.name.range[1] - 1,
52+
];
53+
}
54+
55+
openingElement = {
56+
type: 'JSXOpeningElement',
57+
selfClosing: node.selfClosing,
58+
attributes: node.attributes,
59+
parent: node,
60+
range: [node.range[0], openEnd.range[1]],
61+
loc: {
62+
start: Object.assign({}, node.loc.start),
63+
end: Object.assign({}, node.loc.end),
64+
},
65+
};
66+
openingElement.loc.end = sourceCode.getLocFromIndex(openEnd.range[1]);
67+
openingElement.name = { ...openingElement, type: 'JSXIdentifier' };
68+
openingElement.name.range = [openingElement.name.range[0] + 1, openingElement.name.range[1] - 1];
69+
}
70+
if (node.type === 'GlimmerBlockStatement') {
71+
const tokens = sourceCode.getTokens(node);
72+
let openEndIdx = tokens.findIndex(t => t.value === '}');
73+
while (tokens[openEndIdx + 1].value === '}') {
74+
openEndIdx += 1;
75+
}
76+
const openEnd = tokens[openEndIdx];
77+
let closeStartIdx = tokens.findLastIndex(t => t.value === '{');
78+
while (tokens[closeStartIdx - 1].value === '{') {
79+
closeStartIdx -= 1;
80+
}
81+
const closeStart = tokens[closeStartIdx];
82+
closingElement = {
83+
type: 'JSXClosingElement',
84+
parent: node,
85+
range: [closeStart.range[0], node.range[1]],
86+
loc: {
87+
start: Object.assign({}, node.loc.start),
88+
end: Object.assign({}, node.loc.end),
89+
},
90+
};
91+
closingElement.loc.start = sourceCode.getLocFromIndex(closeStart.range[0]);
92+
93+
openingElement = {
94+
type: 'JSXOpeningElement',
95+
attributes: node.params,
96+
parent: node,
97+
range: [node.range[0], openEnd.range[1]],
98+
loc: {
99+
start: Object.assign({}, node.loc.start),
100+
end: Object.assign({}, node.loc.end),
101+
},
102+
};
103+
openingElement.loc.end = sourceCode.getLocFromIndex(openEnd.range[1]);
104+
}
105+
return {
106+
type: 'JSXElement',
107+
openingElement,
108+
closingElement,
109+
children: node.children || node.body,
110+
parent: node.parent,
111+
range: node.range,
112+
loc: node.loc,
113+
};
114+
}
115+
116+
const ignoredStack = new Set();
117+
118+
return Object.assign({}, rules, {
119+
// overwrite the base rule here so we can use our KNOWN_NODES list instead
120+
'*:exit'(node) {
121+
// For nodes we care about, skip the default handling, because it just marks the node as ignored...
122+
if (
123+
!node.type.startsWith('Glimmer') ||
124+
ignoredStack.size > 0 && !ignoredStack.has(node)
125+
) {
126+
rules['*:exit'](node);
127+
}
128+
if (ignoredStack.has(node)) {
129+
ignoredStack.delete(node);
130+
}
131+
},
132+
'GlimmerTemplate:exit'(node) {
133+
if (!node.parent) {
134+
rules['Program:exit'](node);
135+
}
136+
},
137+
GlimmerElementNode(node) {
138+
if (ignoredStack.size > 0) {
139+
return;
140+
}
141+
if (IGNORED_ELEMENTS.has(node.tag)) {
142+
ignoredStack.add(node);
143+
}
144+
const jsx = JSXElement(node);
145+
rules['JSXElement'](jsx);
146+
rules['JSXOpeningElement'](jsx.openingElement);
147+
if (jsx.closingElement) {
148+
rules['JSXClosingElement'](jsx.closingElement);
149+
}
150+
},
151+
GlimmerAttrNode(node) {
152+
if (ignoredStack.size > 0 || !node.value) {
153+
return;
154+
}
155+
rules['JSXAttribute[value]']({
156+
...node,
157+
type: 'JSXAttribute',
158+
name: {
159+
type: 'JSXIdentifier',
160+
name: node.name,
161+
range: [node.range[0], node.range[0] + node.name.length - 1],
162+
},
163+
});
164+
},
165+
GlimmerTemplate(node) {
166+
if (!node.parent) {
167+
return;
168+
}
169+
const jsx = JSXElement({ ...node, tag: 'template', type: 'GlimmerElementNode' });
170+
rules['JSXElement'](jsx);
171+
},
172+
GlimmerBlockStatement(node) {
173+
const body = [...node.program.body, ...(node.inverse?.body || [])];
174+
rules['JSXElement'](JSXElement({ ...node, body }));
175+
},
176+
});
177+
},
178+
};

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"snake-case": "^3.0.3"
8787
},
8888
"devDependencies": {
89+
"@types/eslint": "^8.44.2",
8990
"@babel/plugin-proposal-class-properties": "^7.13.0",
9091
"@babel/plugin-proposal-decorators": "^7.15.8",
9192
"@types/eslint": "^8.44.2",
@@ -99,7 +100,7 @@
99100
"eslint-plugin-import": "^2.26.0",
100101
"eslint-plugin-jest": "^27.0.1",
101102
"eslint-plugin-markdown": "^3.0.0",
102-
"eslint-plugin-node": "^11.1.0",
103+
"eslint-plugin-n": "^16.0.2",
103104
"eslint-plugin-prettier": "^4.0.0",
104105
"eslint-plugin-unicorn": "^46.0.1",
105106
"eslint-remote-tester": "^3.0.0",

0 commit comments

Comments
 (0)