UNPKG

@kws3/eslint-plugin-svelte4

Version:

Kws3 specific svelte4 eslint rules

236 lines (204 loc) 5.63 kB
const createRule = require("../utils"); const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u; const hasOnlyWhitespace = (value) => /^[\r\n\s\t\f\v]+$/.test(value); function parseTargetAttrs(options) { const regexps = []; for (const tagName of Object.keys(options)) { const attrs = new Set(options[tagName]); regexps.push({ name: toRegExp(tagName), attrs, }); } return regexps; } function toRegExp(str) { const parts = RE_REGEXP_STR.exec(str); if (parts) { return new RegExp(parts[1], parts[2]); } return new RegExp(`^${encodeURI(str)}$`); } function getTargetAttrs(tagName, config) { const result = []; for (const { name, attrs } of config.attributes) { name.lastIndex = 0; if (name.test(tagName)) { result.push(...attrs); } } return new Set(result); } function checkSvelteLiteralOrText(context, literal, config) { if (testValue(literal.value, config)) { return; } const loc = literal.loc; context.report({ loc, message: `Raw text '${literal.value.trim()}' is used. Use {$T.Something()}`, }); } function testValue(value, config) { if (typeof value === "string") { return ( hasOnlyWhitespace(value) || config.ignorePattern.test(value.trim()) || config.ignoreText.includes(value.trim()) ); } else { return false; } } function checkSvelteMustacheTagText(context, node, config) { if (!node.expression || !node.parent) { return; } if (node.parent.type === "SvelteElement") { checkExpressionText(context, node.expression, config); } } function checkExpressionText(context, expression, config) { if (expression.type === "Literal") { checkLiteral(context, expression, config); } else if (isStaticTemplateLiteral(expression)) { checkLiteral(context, expression, config); } else if (expression.type === "ConditionalExpression") { const targets = [expression.consequent, expression.alternate]; targets.forEach((target) => { if (target.type === "Literal") { checkLiteral(context, target, config); } else if (isStaticTemplateLiteral(target)) { checkLiteral(context, target, config); } }); } } function checkLiteral(context, literal, config) { const value = literal.type !== "TemplateLiteral" ? literal.value : literal.quasis[0].value.cooked; if (testValue(value, config)) { return; } const loc = literal.loc; context.report({ loc, message: `Raw text '${value.trim()}' is used. Use {$T.Something()}`, }); } function isStaticTemplateLiteral(node) { return Boolean( node && node.type === "TemplateLiteral" && node.expressions.length === 0 ); } function create(context) { const sourceCode = context.getSourceCode(), config = { attributes: [], ignorePattern: /^$/, ignoreNodes: [], ignoreText: [], }; if (context.options[0]?.ignorePattern) { config.ignorePattern = new RegExp(context.options[0].ignorePattern, "u"); } if (context.options[0]?.ignoreNodes) { config.ignoreNodes = context.options[0].ignoreNodes; } if (context.options[0]?.ignoreText) { config.ignoreText = context.options[0].ignoreText; } if (context.options[0]?.attributes) { config.attributes = parseTargetAttrs(context.options[0].attributes); } function isIgnore(node) { const element = getElement(node); return ( !element || config.ignoreNodes.includes(sourceCode.text.slice(...element.name.range)) ); } function getElement(node) { let target = node.parent; while ( target.type === "SvelteIfBlock" || target.type === "SvelteElseBlock" || target.type === "SvelteEachBlock" || target.type === "SvelteAwaitBlock" || target.type === "SvelteAwaitPendingBlock" || target.type === "SvelteAwaitThenBlock" || target.type === "SvelteAwaitCatchBlock" || target.type === "SvelteKeyBlock" ) { target = target.parent; } if (target.type === "SvelteElement") { return target; } return null; } return { SvelteAttribute(node) { if (node.value.length !== 1 || node.value[0].type !== "SvelteLiteral") { return; } const nameNode = node.parent.parent.name; const tagName = sourceCode.text.slice(...nameNode.range); const attrName = node.key.name; if (!getTargetAttrs(tagName, config).has(attrName)) { return; } checkSvelteLiteralOrText(context, node.value[0], config); }, SvelteMustacheTag(node) { if (isIgnore(node)) { return; } checkSvelteMustacheTagText(context, node, config); }, SvelteText(node) { if (isIgnore(node)) { return; } checkSvelteLiteralOrText(context, node, config); }, }; } module.exports = createRule("no-raw-text", { meta: { docs: { description: "Disallow string literals and enforce translatable strings", }, recommended: false, schema: [ { type: "object", properties: { attributes: { type: "object", patternProperties: { "^(?:\\S+|/.*/[a-z]*)$": { type: "array", items: { type: "string" }, uniqueItems: true, }, }, additionalProperties: false, }, ignoreNodes: { type: "array", }, ignorePattern: { type: "string", }, ignoreText: { type: "array", }, }, }, ], }, create, });