diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fedda17 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + // Extension: ESLint + "eslint.options": { + "rulePaths": [ + "eslint-internal-rules" + ] + }, +} \ No newline at end of file diff --git a/eslint-internal-rules/.eslintrc.cjs b/eslint-internal-rules/.eslintrc.cjs new file mode 100644 index 0000000..faa24b6 --- /dev/null +++ b/eslint-internal-rules/.eslintrc.cjs @@ -0,0 +1,14 @@ +module.exports = { + rules: { + 'valid-appcardcode-code-prop': 'error', + 'valid-appcardcode-demo-sfc': 'error', + }, + overrides: [ + { + files: ['*.json'], + rules: { + 'no-invalid-meta': 'off', + }, + }, + ], +} diff --git a/eslint-internal-rules/package.json b/eslint-internal-rules/package.json new file mode 100644 index 0000000..5bbefff --- /dev/null +++ b/eslint-internal-rules/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/eslint-internal-rules/valid-appcardcode-code-prop.js b/eslint-internal-rules/valid-appcardcode-code-prop.js new file mode 100644 index 0000000..ec5380a --- /dev/null +++ b/eslint-internal-rules/valid-appcardcode-code-prop.js @@ -0,0 +1,53 @@ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const utils = require('eslint-plugin-vue/lib/utils') + +function toCamelCase(str) { + return str.toLowerCase().replace(/[^a-z\d]+(.)/gi, (m, chr) => chr.toUpperCase()) +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'require valid prop for "AppCardCode" component', + categories: ['base'], + url: 'https://eslint.vuejs.org/rules/require-component-is.html', + }, + fixable: null, + schema: [], + }, + + /** @param {RuleContext} context */ + create(context) { + return utils.defineTemplateBodyVisitor(context, { + /** @param {VElement} node */ + 'VElement[name=\'appcardcode\']': function (node) { + const propTitle = utils.getAttribute(node, 'title') + const propCode = utils.getDirective(node, 'bind', 'code') + + const titleValue = propTitle.value.value + const demoCodeProperty = propCode.value.expression.property.name + + const camelCasedTitle = toCamelCase(titleValue) + + if (camelCasedTitle !== demoCodeProperty) { + context.report({ + node, + loc: propCode.value.expression.property.loc, + message: `Expected 'code' prop value to match the camelcase value of title prop value. Expected: '${camelCasedTitle}', Actual: '${demoCodeProperty}'`, + }) + } + }, + }) + }, +} diff --git a/eslint-internal-rules/valid-appcardcode-demo-sfc.js b/eslint-internal-rules/valid-appcardcode-demo-sfc.js new file mode 100644 index 0000000..8bc86fe --- /dev/null +++ b/eslint-internal-rules/valid-appcardcode-demo-sfc.js @@ -0,0 +1,82 @@ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const utils = require('eslint-plugin-vue/lib/utils') + +function toPascalCase(str) { + const words = str.match(/[a-z]+/gi) + if (!words) + return '' + + return words + .map(word => { + return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() + }) + .join('') +} + +function findDemoElement(node) { + let el = null + node.children.forEach(child => { + if (child.children && child.children.length) { + const r = findDemoElement(child) + if (r) + el = r + } + + else { + const r = child.type === 'VElement' && child.name.startsWith('demo') + if (r) + el = child + } + }) + + return el +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'require valid demo SFC for "AppCardCode" component', + categories: ['base'], + url: 'https://eslint.vuejs.org/rules/require-component-is.html', + }, + fixable: null, + schema: [], + }, + + /** @param {RuleContext} context */ + create(context) { + return utils.defineTemplateBodyVisitor(context, { + /** @param {VElement} node */ + 'VElement[name=\'appcardcode\']': function (node) { + const propTitle = utils.getAttribute(node, 'title') + const titleValue = propTitle.value.value + + const pascalCaseTitle = toPascalCase(titleValue) + + const demoNode = findDemoElement(node) + const demoNodeSfcName = demoNode.rawName + const pattern = new RegExp(`Demo[a-zA-z]+${pascalCaseTitle}`) + const demoSfcMatch = demoNodeSfcName.search(pattern) + + if (demoSfcMatch !== 0) { + context.report({ + node, + loc: demoNode.loc, + message: `Expected Demo SFC to match the pascal case value of title prop value. Expected: 'Demo[a-zA-Z]+${pascalCaseTitle}', Actual: '${demoNodeSfcName}'`, + }) + } + }, + }) + }, +}