You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
3.9 KiB
151 lines
3.9 KiB
/** |
|
* @fileoverview Require component name property to match its file name |
|
* @author Rodrigo Pedra Brum <rodrigo.pedra@gmail.com> |
|
*/ |
|
'use strict' |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Requirements |
|
// ------------------------------------------------------------------------------ |
|
|
|
const utils = require('../utils') |
|
const casing = require('../utils/casing') |
|
const path = require('path') |
|
|
|
// ------------------------------------------------------------------------------ |
|
// Rule Definition |
|
// ------------------------------------------------------------------------------ |
|
|
|
module.exports = { |
|
meta: { |
|
type: 'suggestion', |
|
docs: { |
|
description: 'require component name property to match its file name', |
|
categories: undefined, |
|
url: 'https://eslint.vuejs.org/rules/match-component-file-name.html' |
|
}, |
|
fixable: null, |
|
schema: [ |
|
{ |
|
type: 'object', |
|
properties: { |
|
extensions: { |
|
type: 'array', |
|
items: { |
|
type: 'string' |
|
}, |
|
uniqueItems: true, |
|
additionalItems: false |
|
}, |
|
shouldMatchCase: { |
|
type: 'boolean' |
|
} |
|
}, |
|
additionalProperties: false |
|
} |
|
] |
|
}, |
|
/** @param {RuleContext} context */ |
|
create(context) { |
|
const options = context.options[0] |
|
const shouldMatchCase = (options && options.shouldMatchCase) || false |
|
const extensionsArray = options && options.extensions |
|
const allowedExtensions = Array.isArray(extensionsArray) |
|
? extensionsArray |
|
: ['jsx'] |
|
|
|
const extension = path.extname(context.getFilename()) |
|
const filename = path.basename(context.getFilename(), extension) |
|
|
|
/** @type {Rule.ReportDescriptor[]} */ |
|
const errors = [] |
|
let componentCount = 0 |
|
|
|
if (!allowedExtensions.includes(extension.replace(/^\./, ''))) { |
|
return {} |
|
} |
|
|
|
// ---------------------------------------------------------------------- |
|
// Private |
|
// ---------------------------------------------------------------------- |
|
|
|
/** |
|
* @param {string} name |
|
* @param {string} filename |
|
*/ |
|
function compareNames(name, filename) { |
|
if (shouldMatchCase) { |
|
return name === filename |
|
} |
|
|
|
return ( |
|
casing.pascalCase(name) === filename || |
|
casing.kebabCase(name) === filename |
|
) |
|
} |
|
|
|
/** |
|
* @param {Literal | TemplateLiteral} node |
|
*/ |
|
function verifyName(node) { |
|
let name |
|
if (node.type === 'TemplateLiteral') { |
|
const quasis = node.quasis[0] |
|
name = quasis.value.cooked |
|
} else { |
|
name = `${node.value}` |
|
} |
|
|
|
if (!compareNames(name, filename)) { |
|
errors.push({ |
|
node, |
|
message: |
|
'Component name `{{name}}` should match file name `{{filename}}`.', |
|
data: { filename, name } |
|
}) |
|
} |
|
} |
|
|
|
/** |
|
* @param {Expression | SpreadElement} node |
|
* @returns {node is (Literal | TemplateLiteral)} |
|
*/ |
|
function canVerify(node) { |
|
return ( |
|
node.type === 'Literal' || |
|
(node.type === 'TemplateLiteral' && |
|
node.expressions.length === 0 && |
|
node.quasis.length === 1) |
|
) |
|
} |
|
|
|
return Object.assign( |
|
{}, |
|
utils.executeOnCallVueComponent(context, (node) => { |
|
if (node.arguments.length === 2) { |
|
const argument = node.arguments[0] |
|
|
|
if (canVerify(argument)) { |
|
verifyName(argument) |
|
} |
|
} |
|
}), |
|
utils.executeOnVue(context, (object) => { |
|
const node = utils.findProperty(object, 'name') |
|
|
|
componentCount++ |
|
|
|
if (!node) return |
|
if (!canVerify(node.value)) return |
|
verifyName(node.value) |
|
}), |
|
{ |
|
'Program:exit'() { |
|
if (componentCount > 1) return |
|
|
|
errors.forEach((error) => context.report(error)) |
|
} |
|
} |
|
) |
|
} |
|
}
|
|
|