Skip to content

Commit

Permalink
Merge pull request #17 from selaux/add-multiple-transforms
Browse files Browse the repository at this point in the history
Add support for multiple transforms
  • Loading branch information
selaux committed Apr 14, 2017
2 parents 7be83f6 + e2f6c8d commit 913873e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 22 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ module.exports = someVariable;
export default { foo: "bar" };
```

If your filename policy doesn't quite match with your variable naming policy, you can add a tansform:
If your filename policy doesn't quite match with your variable naming policy, you can add one or multiple tansforms:

```json
"filenames/match-exported": [2, "kebab"]
"filenames/match-exported": [ 2, "kebab" ]
```

Now, in your code:
Expand All @@ -83,12 +83,18 @@ Available transforms:
'[camel](https://www.npmjs.com/package/lodash.camelcase)', and
'pascal' (camel-cased with first letter in upper case).

For multiple transforms simply specify an array like this (null in this case stands for no transform):

```json
"filenames/match-exported": [2, [ null, "kebab", "snake" ] ]
```

If you prefer to use suffixes for your files (e.g. `Foo.react.js` for a React component file),
you can use a second configuration parameter. It allows you to remove parts of a filename matching a regex pattern
before transforming and matching against the export.

```json
"filenames/match-exported": [2, "", "\\.react$"]
"filenames/match-exported": [ 2, null, "\\.react$" ]
```

Now, in your code:
Expand Down
56 changes: 44 additions & 12 deletions lib/rules/match-exported.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ var path = require('path'),
pascal: function (name) {
return upperFirst(camelCase(name));
}
};
},
transformNames = Object.keys(transforms),
transformSchema = { "enum": transformNames.concat([ null ]) };

function getStringToCheckAgainstExport(parsed, replacePattern) {
var dirArray = parsed.dir.split(path.sep);
Expand All @@ -34,16 +36,40 @@ function getStringToCheckAgainstExport(parsed, replacePattern) {
}
}

function transform(exportedName, transformName) {
var transform = transforms[transformName];
function getTransformsFromOptions(option) {
var usedTransforms = (option && option.push) ? option : [ option ];

return usedTransforms.map(function (name) {
return transforms[name];
});
}

function transform(exportedName, transforms) {
return transforms.map(function (t) {
return t ? t(exportedName) : exportedName;
});
}

return transform ? transform(exportedName) : exportedName;
function anyMatch(expectedExport, transformedNames) {
return transformedNames.some(function (name) {
return name === expectedExport;
});
}

function getWhatToMatchMessage(transforms) {
if (transforms.length === 1 && !transforms[0]) {
return "the exported name";
}
if (transforms.length > 1) {
return "any of the exported and transformed names"
}
return "the exported and transformed name";
}

module.exports = function(context) {
return {
"Program": function (node) {
var transformName = context.options[0],
var transforms = getTransformsFromOptions(context.options[0]),
replacePattern = context.options[1] ? new RegExp(context.options[1]) : null,
filename = context.getFilename(),
absoluteFilename = path.resolve(filename),
Expand All @@ -52,26 +78,28 @@ module.exports = function(context) {
exportedName = getExportedName(node),
isExporting = Boolean(exportedName),
expectedExport = getStringToCheckAgainstExport(parsed, replacePattern),
transformedName = transform(exportedName, transformName),
transformedNames = transform(exportedName, transforms),
everythingIsIndex = exportedName === 'index' && parsed.name === 'index',
matchesExported = transformedName === expectedExport || everythingIsIndex,
matchesExported = anyMatch(expectedExport, transformedNames) || everythingIsIndex,
reportIf = function (condition, messageForNormalFile, messageForIndexFile) {
var message = (!messageForIndexFile || !isIndexFile(parsed)) ? messageForNormalFile : messageForIndexFile;

if (condition) {
context.report(node, message, {
name: parsed.base,
expectedExport: expectedExport,
exportName: transformedName,
extension: parsed.ext
exportName: transformedNames.join("', '"),
extension: parsed.ext,
whatToMatch: getWhatToMatchMessage(transforms)
});
}
};

if (shouldIgnore) return;

reportIf(
isExporting && !matchesExported,
"Filename '{{expectedExport}}' must match the exported name '{{exportName}}'.",
"Filename '{{expectedExport}}' must match {{whatToMatch}} '{{exportName}}'.",
"The directory '{{expectedExport}}' must be named '{{exportName}}', after the exported value of its index file."
);
}
Expand All @@ -80,9 +108,13 @@ module.exports = function(context) {

module.exports.schema = [
{
"enum": ["", "kebab", "snake", "camel", "pascal"]

oneOf: [
transformSchema,
{ type: "array", items: transformSchema, minItems: 1 }
]
},
{
"type": "string"
type: "string"
}
];
44 changes: 37 additions & 7 deletions test/rules/match-exported.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,17 +255,29 @@ ruleTester.run("lib/rules/match-exported with configuration", exportedRule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: ['pascal']
},
{
code: snakeCaseEs6,
filename: "variableName.js",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: [ [ 'pascal', 'camel' ] ]
},
{
code: snakeCaseEs6,
filename: "VariableName.js",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: [ [ 'pascal', 'camel' ] ]
},
{
code: exportedJsxClassCode,
filename: "/some/dir/Foo.react.js",
parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } },
options: ["", "\\.react$"]
options: [null, "\\.react$"]
},
{
code: exportedEs6JsxClassCode,
filename: "/some/dir/Foo.react.js",
parserOptions: { ecmaVersion: 6, sourceType: "module", ecmaFeatures: { jsx: true } },
options: ["", "\\.react$"]
options: [null, "\\.react$"]
}
],

Expand All @@ -275,7 +287,7 @@ ruleTester.run("lib/rules/match-exported with configuration", exportedRule, {
filename: "variableName.js",
options: ['snake'],
errors: [
{ message: "Filename 'variableName' must match the exported name 'variable_name'.", column: 1, line: 1 }
{ message: "Filename 'variableName' must match the exported and transformed name 'variable_name'.", column: 1, line: 1 }
]
},
{
Expand All @@ -284,7 +296,7 @@ ruleTester.run("lib/rules/match-exported with configuration", exportedRule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: ['kebab'],
errors: [
{ message: "Filename 'variableName' must match the exported name 'variable-name'.", column: 1, line: 1 }
{ message: "Filename 'variableName' must match the exported and transformed name 'variable-name'.", column: 1, line: 1 }
]
},
{
Expand All @@ -293,14 +305,32 @@ ruleTester.run("lib/rules/match-exported with configuration", exportedRule, {
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: ['pascal'],
errors: [
{ message: "Filename 'variableName' must match the exported name 'VariableName'.", column: 1, line: 1 }
{ message: "Filename 'variableName' must match the exported and transformed name 'VariableName'.", column: 1, line: 1 }
]
},
{
code: camelCaseEs6,
filename: "VariableName.js",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: [ [ null ] ],
errors: [
{ message: "Filename 'VariableName' must match the exported name 'variableName'.", column: 1, line: 1 }
]
},
{
code: camelCaseEs6,
filename: "variableName.js",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
options: [ ['pascal', 'snake' ] ],
errors: [
{ message: "Filename 'variableName' must match any of the exported and transformed names 'VariableName', 'variable_name'.", column: 1, line: 1 }
]
},
{
code: exportedEs6JsxClassCode,
filename: "/some/dir/Foo.bar.js",
parserOptions: { ecmaVersion: 6, sourceType: "module", ecmaFeatures: { jsx: true } },
options: ["", "\\.react$"],
options: [null, "\\.react$"],
errors: [
{ message: "Filename 'Foo.bar' must match the exported name 'Foo'.", column: 1, line: 1 }
]
Expand All @@ -309,7 +339,7 @@ ruleTester.run("lib/rules/match-exported with configuration", exportedRule, {
code: exportedEs6JsxClassCode,
filename: "/some/dir/Foo.react/index.js",
parserOptions: { ecmaVersion: 6, sourceType: "module", ecmaFeatures: { jsx: true } },
options: ["", "\\.react$"],
options: [null, "\\.react$"],
errors: [
{ message: "The directory 'Foo.react' must be named 'Foo', after the exported value of its index file.", column: 1, line: 1 }
]
Expand Down

0 comments on commit 913873e

Please sign in to comment.