UNPKG

eslint-plugin-ember

Version:
170 lines (159 loc) 5.55 kB
function attrValueHasChar(node, ch) { if (node.type === 'GlimmerTextNode') { return node.chars.includes(ch); } if (node.type === 'GlimmerConcatStatement') { return (node.parts || []).some((n) => n.type === 'GlimmerTextNode' && n.chars.includes(ch)); } return false; } /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'layout', docs: { description: 'enforce consistent quote style in templates', category: 'Stylistic Issues', url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-quotes.md', templateMode: 'both', }, fixable: 'code', schema: [ { oneOf: [ { enum: ['double', 'single'] }, // `false` as the root config disables the rule, matching upstream // ember-template-lint which accepts `[rule, false]` as a valid // disabled state without schema errors. { type: 'boolean', enum: [false] }, { type: 'object', required: ['curlies', 'html'], properties: { curlies: { enum: ['double', 'single', false] }, html: { enum: ['double', 'single', false] }, }, additionalProperties: false, }, ], }, ], messages: { wrongQuotes: '{{message}}', }, originallyFrom: { name: 'ember-template-lint', rule: 'lib/rules/quotes.js', docs: 'docs/rule/quotes.md', tests: 'test/unit/rules/quotes-test.js', }, }, create(context) { const rawOption = context.options[0]; // Disabled when options omitted or explicitly set to `false`. if (!rawOption) { return {}; } const config = typeof rawOption === 'string' ? { curlies: rawOption, html: rawOption } : rawOption; if (!config.curlies && !config.html) { return {}; } const chars = { single: "'", double: '"' }; const goodChars = { curlies: config.curlies ? chars[config.curlies] : null, html: config.html ? chars[config.html] : null, }; const badChars = { curlies: goodChars.curlies ? goodChars.curlies === chars.single ? chars.double : chars.single : null, html: goodChars.html ? (goodChars.html === chars.single ? chars.double : chars.single) : null, }; let message; if (goodChars.curlies === goodChars.html && goodChars.curlies) { message = `you must use ${config.curlies} quotes in templates`; } else if (!goodChars.curlies || !goodChars.html) { const active = goodChars.curlies || goodChars.html; const activeType = active === chars.single ? 'single' : 'double'; const context_ = goodChars.curlies ? 'Handlebars syntax' : 'HTML attributes'; message = `you must use ${activeType} quotes in ${context_}`; } else { const doubleCtx = goodChars.curlies === chars.double ? 'Handlebars syntax' : 'HTML attributes'; const singleCtx = goodChars.curlies === chars.single ? 'Handlebars syntax' : 'HTML attributes'; message = `you must use double quotes for ${doubleCtx} and single quotes for ${singleCtx} in templates`; } const sourceCode = context.sourceCode; return { GlimmerAttrNode(node) { if (!goodChars.html || !badChars.html) { return; } // Skip valueless attributes if (!node.value || node.isValueless) { return; } const raw = sourceCode.getText(node); // Extract quote char used: attr="..." or attr='...' const eqIndex = raw.indexOf('='); if (eqIndex === -1) { return; } const afterEq = raw[eqIndex + 1]; if (afterEq !== badChars.html) { return; } // If the value contains the desired quote char, we can't autofix if (attrValueHasChar(node.value, goodChars.html)) { context.report({ node, messageId: 'wrongQuotes', data: { message } }); return; } context.report({ node, messageId: 'wrongQuotes', data: { message }, fix(fixer) { const nodeText = sourceCode.getText(node); const eqIdx = nodeText.indexOf('='); const valuePart = nodeText.slice(eqIdx + 1); // Replace outer quotes const newValuePart = goodChars.html + valuePart.slice(1, -1) + goodChars.html; const newText = nodeText.slice(0, eqIdx + 1) + newValuePart; return fixer.replaceText(node, newText); }, }); }, GlimmerStringLiteral(node) { if (!goodChars.curlies || !badChars.curlies) { return; } const raw = sourceCode.getText(node); if (!raw || raw.length < 2) { return; } const usedQuote = raw[0]; if (usedQuote !== badChars.curlies) { return; } // If the value contains the desired quote char, we can't autofix if (node.value && node.value.includes(goodChars.curlies)) { context.report({ node, messageId: 'wrongQuotes', data: { message } }); return; } context.report({ node, messageId: 'wrongQuotes', data: { message }, fix(fixer) { const newText = goodChars.curlies + raw.slice(1, -1) + goodChars.curlies; return fixer.replaceText(node, newText); }, }); }, }; }, };