Skip to content

Commit

Permalink
implement indent
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklx committed Aug 23, 2023
1 parent fef70d7 commit 45b1e2a
Show file tree
Hide file tree
Showing 4 changed files with 441 additions and 6 deletions.
10 changes: 5 additions & 5 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = {
'plugin:jest/style',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:node/recommended',
'plugin:n/recommended',
'plugin:unicorn/recommended',
'prettier',
],
Expand Down Expand Up @@ -188,10 +188,10 @@ module.exports = {
'no-unused-labels': 'off',
'no-unused-vars': 'off',
'no-useless-constructor': 'off',
'node/no-extraneous-import': 'off',
'node/no-missing-import': 'off',
'node/no-missing-require': 'off',
'node/no-unsupported-features/es-syntax': 'off',
'n/no-extraneous-import': 'off',
'n/no-missing-import': 'off',
'n/no-missing-require': 'off',
'n/no-unsupported-features/es-syntax': 'off',
'prettier/prettier': ['error', { trailingComma: 'none' }],
'unicorn/filename-case': 'off',
},
Expand Down
178 changes: 178 additions & 0 deletions lib/rules/template-indent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const { builtinRules } = require('eslint/use-at-your-own-risk');

const baseRule = builtinRules.get('indent');
const IGNORED_ELEMENTS = new Set(['pre', 'script', 'style', 'textarea']);

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
ERROR_MESSAGE: baseRule.meta.messages.wrongIndentation,
name: 'indent',
meta: {
type: 'layout',
docs: {
description: 'enforce consistent indentation',
// too opinionated to be recommended
extendsBaseRule: true,
recommended: true,
category: 'Ember Octane',
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-indent.md',
},
fixable: 'whitespace',
hasSuggestions: baseRule.meta.hasSuggestions,
schema: baseRule.meta.schema,
messages: baseRule.meta.messages,
},

create: (context) => {
const rules = baseRule.create(context);
const sourceCode = context.sourceCode;

function JSXElement(node) {
let closingElement;
let openingElement;
if (node.type === 'GlimmerElementNode') {
const tokens = sourceCode.getTokens(node);
const openEnd = tokens.find(t => t.value === '>');
const closeStart = tokens.findLast(t => t.value === '<');
if (!node.selfClosing) {
closingElement = {
type: 'JSXClosingElement',
parent: node,
range: [closeStart.range[0], node.range[1]],
loc: {
start: Object.assign({}, node.loc.start),
end: Object.assign({}, node.loc.end),
},
};
closingElement.loc.start = sourceCode.getLocFromIndex(closeStart.range[0]);
closingElement.name = { ...closingElement, type: 'JSXIdentifier' };
closingElement.name.range = [
closingElement.name.range[0] + 1,
closingElement.name.range[1] - 1,
];
}

openingElement = {
type: 'JSXOpeningElement',
selfClosing: node.selfClosing,
attributes: node.attributes,
parent: node,
range: [node.range[0], openEnd.range[1]],
loc: {
start: Object.assign({}, node.loc.start),
end: Object.assign({}, node.loc.end),
},
};
openingElement.loc.end = sourceCode.getLocFromIndex(openEnd.range[1]);
openingElement.name = { ...openingElement, type: 'JSXIdentifier' };
openingElement.name.range = [openingElement.name.range[0] + 1, openingElement.name.range[1] - 1];
}
if (node.type === 'GlimmerBlockStatement') {
const tokens = sourceCode.getTokens(node);
let openEndIdx = tokens.findIndex(t => t.value === '}');
while (tokens[openEndIdx + 1].value === '}') {
openEndIdx += 1;
}
const openEnd = tokens[openEndIdx];
let closeStartIdx = tokens.findLastIndex(t => t.value === '{');
while (tokens[closeStartIdx - 1].value === '{') {
closeStartIdx -= 1;
}
const closeStart = tokens[closeStartIdx];
closingElement = {
type: 'JSXClosingElement',
parent: node,
range: [closeStart.range[0], node.range[1]],
loc: {
start: Object.assign({}, node.loc.start),
end: Object.assign({}, node.loc.end),
},
};
closingElement.loc.start = sourceCode.getLocFromIndex(closeStart.range[0]);

openingElement = {
type: 'JSXOpeningElement',
attributes: node.params,
parent: node,
range: [node.range[0], openEnd.range[1]],
loc: {
start: Object.assign({}, node.loc.start),
end: Object.assign({}, node.loc.end),
},
};
openingElement.loc.end = sourceCode.getLocFromIndex(openEnd.range[1]);
}
return {
type: 'JSXElement',
openingElement,
closingElement,
children: node.children || node.body,
parent: node.parent,
range: node.range,
loc: node.loc,
};
}

const ignoredStack = new Set();

return Object.assign({}, rules, {
// overwrite the base rule here so we can use our KNOWN_NODES list instead
'*:exit'(node) {
// For nodes we care about, skip the default handling, because it just marks the node as ignored...
if (
!node.type.startsWith('Glimmer') ||
ignoredStack.size > 0 && !ignoredStack.has(node)
) {
rules['*:exit'](node);
}
if (ignoredStack.has(node)) {
ignoredStack.delete(node);
}
},
'GlimmerTemplate:exit'(node) {
if (!node.parent) {
rules['Program:exit'](node);
}
},
GlimmerElementNode(node) {
if (ignoredStack.size > 0) {
return;
}
if (IGNORED_ELEMENTS.has(node.tag)) {
ignoredStack.add(node);
}
const jsx = JSXElement(node);
rules['JSXElement'](jsx);
rules['JSXOpeningElement'](jsx.openingElement);
if (jsx.closingElement) {
rules['JSXClosingElement'](jsx.closingElement);
}
},
GlimmerAttrNode(node) {
if (ignoredStack.size > 0 || !node.value) {
return;
}
rules['JSXAttribute[value]']({
...node,
type: 'JSXAttribute',
name: {
type: 'JSXIdentifier',
name: node.name,
range: [node.range[0], node.range[0] + node.name.length - 1],
},
});
},
GlimmerTemplate(node) {
if (!node.parent) {
return;
}
const jsx = JSXElement({ ...node, tag: 'template', type: 'GlimmerElementNode' });
rules['JSXElement'](jsx);
},
GlimmerBlockStatement(node) {
const body = [...node.program.body, ...(node.inverse?.body || [])];
rules['JSXElement'](JSXElement({ ...node, body }));
},
});
},
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"snake-case": "^3.0.3"
},
"devDependencies": {
"@types/eslint": "^8.44.2",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.15.8",
"@types/eslint": "^8.44.2",
Expand All @@ -99,7 +100,7 @@
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^27.0.1",
"eslint-plugin-markdown": "^3.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-n": "^16.0.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unicorn": "^46.0.1",
"eslint-remote-tester": "^3.0.0",
Expand Down
Loading

0 comments on commit 45b1e2a

Please sign in to comment.