Skip to content

Commit

Permalink
implement indent
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklx committed Nov 2, 2023
1 parent fa12979 commit d042605
Show file tree
Hide file tree
Showing 2 changed files with 543 additions and 0 deletions.
194 changes: 194 additions & 0 deletions lib/rules/template-indent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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',
extendsBaseRule: true,
// too opinionated to be recommended
recommended: false,
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 ctx = Object.create(context, {
report: {
writable: false,
configurable: false,
value: (info) => {
const node = context.sourceCode.getNodeByRangeIndex(info.node.range[0]);
if (!node.type.startsWith('Glimmer')) {
return;
}
context.report(info);
},
},
});
const rules = baseRule.create(ctx);
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 }));
},
});
},
};
Loading

0 comments on commit d042605

Please sign in to comment.