Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group capture #17

Merged
merged 3 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/rules/filename-naming-convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ module.exports = {
};
```

#### using capture groups

You can use glob capture groups in you rule set using the `<index>` syntax. Read more about glob capture groups in the [micromatch documentation](https://github.com/micromatch/micromatch#capture).

For example the following rule will only allow a file to be named the same as its parent folder :

```js
module.exports = {
plugins: ['check-file'],
rules: {
'check-file/filename-naming-convention': [
'error',
{
'**/*/*': '<1>',
},
{
ignoreMiddleExtensions: true,
},
],
},
};
```

#### rule configuration object

##### `ignoreMiddleExtensions`
Expand Down
50 changes: 29 additions & 21 deletions lib/rules/filename-naming-convention.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
*/
'use strict';

const { transformRuleWithGroupCapture } = require('../utils/transform');
const { getFilename, getBasename, getFilePath } = require('../utils/filename');
const {
checkSettings,
namingPatternValidator,
fileNamingPatternValidator,
globPatternValidator,
} = require('../utils/settings');
const { getDocUrl } = require('../utils/doc');
Expand Down Expand Up @@ -45,13 +46,16 @@ module.exports = {
create(context) {
return {
Program: (node) => {
const rules = context.options[0];
const filenameWithPath = getFilePath(context);
const filename = getFilename(filenameWithPath);

const rules = context.options[0] || {};
const { ignoreMiddleExtensions } = context.options[1] || {};

const invalidPattern = checkSettings(
rules,
globPatternValidator,
namingPatternValidator
fileNamingPatternValidator
);

if (invalidPattern) {
Expand All @@ -66,29 +70,33 @@ module.exports = {
return;
}

const filenameWithPath = getFilePath(context);
const filename = getFilename(filenameWithPath);
for (const [
originalFexPattern,
originalNamingPattern,
] of Object.entries(rules)) {
try {
const [fexPattern, namingPattern] = transformRuleWithGroupCapture(
[originalFexPattern, originalNamingPattern],
filenameWithPath
);

for (const [fexPattern, namingPattern] of Object.entries(rules)) {
const matchResult = matchRule(
filenameWithPath,
fexPattern,
getBasename(filename, ignoreMiddleExtensions),
namingPattern
);
const matchResult = matchRule(
filenameWithPath,
fexPattern,
getBasename(filename, ignoreMiddleExtensions),
namingPattern
);

if (matchResult) {
const { pattern } = matchResult;
if (matchResult) {
throw new Error(
`The filename "${filename}" does not match the "${originalNamingPattern}" style`
);
}
} catch (error) {
context.report({
node,
message:
'The filename "{{filename}}" does not match the "{{pattern}}" style',
data: {
filename,
pattern,
},
message: error.message,
});
return;
}
}
},
Expand Down
11 changes: 11 additions & 0 deletions lib/utils/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ const namingPatternValidator = (namingPattern) => {
return isGlob(namingPattern) || buildInPatterns.includes(namingPattern);
};

/**
* @returns {boolean} true if pattern is a valid naming pattern
* @param {string} namingPattern pattern string
*/
const fileNamingPatternValidator = (namingPattern) => {
return (
namingPatternValidator(namingPattern) || !!/^<\d+>$/.test(namingPattern)
);
};

/**
* @returns {boolean} true if pattern is a valid glob pattern
* @param {string} pattern pattern string
Expand All @@ -48,5 +58,6 @@ const globPatternValidator = isGlob;
module.exports = {
checkSettings,
namingPatternValidator,
fileNamingPatternValidator,
globPatternValidator,
};
41 changes: 41 additions & 0 deletions lib/utils/transform.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const micromatch = require('micromatch');

/**
* Takes in a ruleset and transforms it if it contains capture groups
*
* @param {Array} ruleset ruleset
* @param {Array} ruleset.0 glob
* @param {Array} ruleset.1 rule to transform
* @param {string} filenameWithPath filename with path
* @returns {Array} [glob, rule]
*/
function transformRuleWithGroupCapture([glob, rule], filenameWithPath) {
const keyCaptureGroups = micromatch.capture(glob, filenameWithPath);

if (!keyCaptureGroups) {
return [glob, rule];
}

const valueCaptureGroupRegex = /<(\d+)>/g;
const valueCaptureGroups = [...rule.matchAll(valueCaptureGroupRegex)];

if (!valueCaptureGroups || !valueCaptureGroups.length) {
return [glob, rule];
}

const newRule = valueCaptureGroups.reduce((value, group) => {
const groupIndex = +group[1];
if (!keyCaptureGroups || keyCaptureGroups[groupIndex] === undefined) {
throw new Error(
`The capture group "${rule}" is not found in the glob "${glob}"`
);
}
return value.replace(group[0], keyCaptureGroups[+group[1]]);
}, rule);

return [glob, newRule];
}

module.exports = {
transformRuleWithGroupCapture,
};
91 changes: 91 additions & 0 deletions tests/lib/rules/filename-naming-convention.posix.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,3 +1271,94 @@ ruleTester.run(
],
}
);

ruleTester.run(
"filename-naming-convention with option: [{ '**/*/!(index).*': '<1>' }, { ignoreMiddleExtensions: true }]",
rule,
{
valid: [
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/index.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/featureA.jsx',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/featureA.specs.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As shown in this test case and the one above, ignoreMiddleExtensions seems cant work well now.


invalid: [
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/featureB.jsx',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
errors: [
{
message:
'The filename "featureB.jsx" does not match the "<1>" style',
column: 1,
line: 1,
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/featureB.specs.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
errors: [
{
message:
'The filename "featureB.specs.js" does not match the "<1>" style',
column: 1,
line: 1,
},
],
},
],
}
);

ruleTester.run(
"filename-naming-convention with option: [{ '**/*/!(index).*': '<9>' }]",
rule,
{
valid: [],
invalid: [
{
code: "var foo = 'bar';",
filename: 'src/components/featureA/featureA.jsx',
options: [{ '**/*/!(index).*': '<9>' }],
errors: [
{
message:
'The capture group "<9>" is not found in the glob "**/*/!(index).*"',
column: 1,
line: 1,
},
],
},
],
}
);
91 changes: 91 additions & 0 deletions tests/lib/rules/filename-naming-convention.windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -828,3 +828,94 @@ ruleTester.run(
],
}
);

ruleTester.run(
"filename-naming-convention with option: [{ '**/*/!(index).*': '<1>' }, { ignoreMiddleExtensions: true }]",
rule,
{
valid: [
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\index.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\featureA.jsx',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\featureA.specs.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
},
],

invalid: [
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\featureB.jsx',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
errors: [
{
message:
'The filename "featureB.jsx" does not match the "<1>" style',
column: 1,
line: 1,
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\featureB.specs.js',
options: [
{ '**/*/!(index).*': '<1>' },
{ ignoreMiddleExtensions: true },
],
errors: [
{
message:
'The filename "featureB.specs.js" does not match the "<1>" style',
column: 1,
line: 1,
},
],
},
],
}
);

ruleTester.run(
"filename-naming-convention with option: [{ '**/*/!(index).*': '<9>' }]",
rule,
{
valid: [],
invalid: [
{
code: "var foo = 'bar';",
filename: 'src\\components\\featureA\\featureA.jsx',
options: [{ '**/*/!(index).*': '<9>' }],
errors: [
{
message:
'The capture group "<9>" is not found in the glob "**/*/!(index).*"',
column: 1,
line: 1,
},
],
},
],
}
);