module.exports = { rules: { // auto fixable 'no-spaces-in-control-flow-statements': { meta: { docs: { description: 'Disallow spaces in control flow statements', category: 'Z8 Stylistic Issues', recommended: true, }, fixable: 'code', schema: [], }, create: function (context) { return { IfStatement: function (node) { const tokens = context.getSourceCode().getTokens(node); const firstToken = tokens[0]; const secondToken = tokens[1]; if (firstToken.type === 'Keyword' && firstToken.value === 'if' && secondToken.type === 'Punctuator' && secondToken.value === '(' && secondToken.range[0] - firstToken.range[1] > 0 ) { context.report({ node: node, message: 'Do not use spaces between \'if\' and its condition', fix: function(fixer) { return fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], ''); } }); } }, ForStatement: function (node) { const tokens = context.getSourceCode().getTokens(node); const firstToken = tokens[0]; const secondToken = tokens[1]; if ( firstToken.type === 'Keyword' && firstToken.value === 'for' && secondToken.type === 'Punctuator' && secondToken.value === '(' && secondToken.range[0] - firstToken.range[1] > 0 ) { context.report({ node: node, message: 'Do not use spaces between \'for\' and its condition', fix: function(fixer) { return fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], ''); } }); } }, WhileStatement: function (node) { const tokens = context.getSourceCode().getTokens(node); const firstToken = tokens[0]; const secondToken = tokens[1]; if ( firstToken.type === 'Keyword' && firstToken.value === 'while' && secondToken.type === 'Punctuator' && secondToken.value === '(' && secondToken.range[0] - firstToken.range[1] > 0 ) { context.report({ node: node, message: 'Do not use spaces between \'while\' and its condition', fix: function(fixer) { return fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], ''); } }); } }, SwitchStatement: function (node) { const tokens = context.getSourceCode().getTokens(node); const firstToken = tokens[0]; const secondToken = tokens[1]; if ( firstToken.type === 'Keyword' && firstToken.value === 'switch' && secondToken.type === 'Punctuator' && secondToken.value === '(' && secondToken.range[0] - firstToken.range[1] > 0 ) { context.report({ node: node, message: 'Do not use spaces between \'switch\' and its expression', fix: function(fixer) { return fixer.replaceTextRange([firstToken.range[1], secondToken.range[0]], ''); } }); } }, }; }, }, 'no-strict-equality': { meta: { docs: { description: 'Disallow strict equality operator', category: 'Z8 Stylistic Issues', recommended: true, }, fixable: 'code', schema: [], }, create: function (context) { return { BinaryExpression: function (node) { if ( (node.operator === '===' || node.operator === '!==') && ( (node.right.type === 'Literal' && node.left.type === 'Literal') || (node.right.type === 'Literal' && ![true, false].includes(node.right.value)) || (node.left.type === 'Literal' && ![true, false].includes(node.left.value)) || (node.right.type === 'Identifier' && node.left.type === 'Identifier' && !(node.right.name === "undefined" || node.left.name === "undefined")) ) ) { context.report({ node: node, message: 'Avoid using strict equality operator \''+node.operator+'\' (use loose equality operator \''+node.operator.substring(0, 2)+'\' instead)', }); } }, }; }, }, // auto fixable 'no-double-quotes': { meta: { docs: { description: 'Disallow double quotes', category: 'Z8 Stylistic Issues', recommended: true, }, fixable: 'code', schema: [], }, create: function (context) { return { Literal: function (node) { if (typeof node.value === 'string' && node.raw[0] === '"') { context.report({ node: node, message: 'Avoid using double quotes (use single quotes instead)', fix: function(fixer) { return fixer.replaceTextRange([node.start, node.end], "'" + node.value.replace(/'/g, '\\\'') + "'"); } }); } }, }; }, }, 'no-arrow-functions': { meta: { docs: { description: 'Disallow arrow functions', category: 'Z8 Stylistic Issues', recommended: true, }, schema: [], }, create: function (context) { return { ArrowFunctionExpression: function (node) { context.report({ node: node, message: 'Avoid using arrow functions', }); }, }; }, }, 'no-function-declaration': { meta: { docs: { description: 'Disallow function declaration', category: 'Z8 Stylistic Issues', recommended: true, }, schema: [], }, create: function (context) { return { FunctionDeclaration: function (node) { context.report({ node: node, message: 'Use function expression instead of function declaration', }); }, VariableDeclarator: function (node) { if ( node.init && node.init.type === 'FunctionExpression' && node.init.id && node.init.id.type === 'Identifier' && node.id && node.id.type === 'Identifier' && node.init.id.name === node.id.name ) { context.report({ node: node, message: 'Use function expression instead of function declaration', }); } }, }; }, }, 'todo': { meta: { type: 'suggestion', docs: { description: 'TODO tracking', category: 'Other', recommended: true }, schema: [] }, create: function (context) { const sourceCode = context.getSourceCode(); const comments = sourceCode.getAllComments(); return { Program: function(node) { comments.forEach(comment => { if (comment.value.toLowerCase().includes('todo')) { context.report({ node, loc: comment.loc, message: comment.value.trim(), }); } }); }, }; } }, // auto fixable 'whitespace-only-lines': { meta: { type: 'suggestion', docs: { description: 'Disallow empty lines with only whitespace', category: 'Z8 Stylistic Issues', recommended: true }, fixable: 'code', schema: [] }, create: function (context) { const sourceCode = context.getSourceCode(); return { Program: function (node) { const lines = sourceCode.lines; lines.forEach((line, index) => { if (/^\s+$/.test(line)) { context.report({ node: node, loc: {line: index + 1, column: 0}, message: 'Empty line with only whitespace', fix: function(fixer) { return fixer.replaceTextRange([sourceCode.getIndexFromLoc({line: index + 1, column: 0}), sourceCode.getIndexFromLoc({line: index + 1, column: line.length})], ''); } }); } }); } }; } }, // auto fixable 'prefer-var': { meta: { docs: { description: 'Disallow let/const usage', category: 'Z8 Stylistic Issues', recommended: true, }, fixable: 'code', schema: [], }, create: function(context) { return { VariableDeclaration(node) { if (node.kind !== 'var') { context.report({ node: node, message: 'Prefer using `var` instead of `let` or `const`.', fix: function(fixer) { return fixer.replaceTextRange([node.start, node.start + node.kind.length], 'var'); } }); } } }; } } }, }; /** * Preferable .eslintrc.js: * * module.exports = { * 'plugins': [ * 'z8' * ], * 'rules': { * 'z8/no-spaces-in-control-flow-statements': ['error'], * 'z8/no-strict-equality': ['error'], * 'z8/no-double-quotes': ['error'], * 'z8/no-arrow-functions': ['error'], * 'z8/no-function-declaration': ['error'], * 'object-shorthand': ['error', 'never'], * 'object-curly-spacing': ['error', 'always'], * 'space-before-blocks': ['error', 'always'], * 'func-call-spacing': ['error', 'never'], * 'space-before-function-paren': ['error', 'never'], * 'semi': ['error', 'always'], * 'space-infix-ops': ['error'], * 'indent': ['error', 'tab'], * 'no-console': ['warn'], * 'z8/todo': ['warn'], * 'z8/whitespace-only-lines': ['error'] * }, * 'parserOptions': { * 'ecmaVersion': 2022, * 'sourceType': 'module' * } * } * */