@leonsilicon/eslint-plugin-filename-export 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +200 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +97 -0
- package/src/index.ts +251 -0
- package/src/types.ts +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[<img src="https://img.shields.io/npm/v/eslint-plugin-filename-export?label=%20&style=for-the-badge&logo=pnpm&logoColor=white">](https://www.npmjs.com/package/eslint-plugin-filename-export)
|
|
2
|
+
<img src="https://img.shields.io/npm/dt/eslint-plugin-filename-export?style=for-the-badge&logo=npm&logoColor=white" >
|
|
3
|
+
[<img src="https://img.shields.io/bundlephobia/minzip/eslint-plugin-filename-export?style=for-the-badge&logo=esbuild&logoColor=white">](https://bundlephobia.com/package/eslint-plugin-filename-export)
|
|
4
|
+
|
|
5
|
+
# eslint-plugin-filename-export
|
|
6
|
+
|
|
7
|
+
This plugin enforces that the filename matches to a named export or default export when a name is provided. This can be set to being case sensitive or case insensitive.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Install with your package manager of choice:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm i -D eslint-plugin-filename-export
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Add to your ESLint config:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"plugins": ["eslint-plugin-filename-export"],
|
|
22
|
+
"rules": {
|
|
23
|
+
"filename-export/match-named-export": "error",
|
|
24
|
+
"filename-export/match-default-export": "error"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- `filename-export/match-named-export` - Enforces that the filename matches to a named export.
|
|
32
|
+
- `filename-export/match-default-export` - Enforces that filenames match the name of the default export.
|
|
33
|
+
|
|
34
|
+
These rules ignore index files, test/spec files, and files that have no relevant exports by default. Additionally, files with a default export will be ignored by the `match-named-export` rule.
|
|
35
|
+
|
|
36
|
+
If you want to add additional filename exemptions, use the ESLint's builting filename overrides.
|
|
37
|
+
|
|
38
|
+
## Options
|
|
39
|
+
|
|
40
|
+
Both of these rules have the following available options:
|
|
41
|
+
|
|
42
|
+
- `casing`:
|
|
43
|
+
|
|
44
|
+
- `strict`: Filenames much match in case to the exports
|
|
45
|
+
- `loose`: Filenames do not need to match case (`default`)
|
|
46
|
+
|
|
47
|
+
- `stripextra`:
|
|
48
|
+
- `true`: Filenames will be stripped of any non-alphanumeric characters (to allow filenames like `great_function.ts` to match `greatfunction`)
|
|
49
|
+
- `false`: Filenames will not be stripped of any extra characters (`default`)
|
|
50
|
+
|
|
51
|
+
These can be passed as a second item in an array to the rule as follows
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
"filename-export/match-named-export": [
|
|
55
|
+
"error",
|
|
56
|
+
{
|
|
57
|
+
"casing": "strict",
|
|
58
|
+
"sripextra": true
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Roadmap
|
|
64
|
+
|
|
65
|
+
This plugin is mainly being produced for personal use. If you are interested in using it, but need additional features, please open an issue and I will consider adding them.
|
|
66
|
+
|
|
67
|
+
Pull requests will also be evaluated and merged as appropriate.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import { Options } from './types';
|
|
3
|
+
declare const _default: {
|
|
4
|
+
rules: {
|
|
5
|
+
'match-named-export': ESLintUtils.RuleModule<"noMatchingExport", Options, unknown, ESLintUtils.RuleListener>;
|
|
6
|
+
'match-default-export': ESLintUtils.RuleModule<"defaultExportDoesNotMatch", Options, unknown, ESLintUtils.RuleListener>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export = _default;
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"src/","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EAEZ,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAsC,OAAO,EAAE,MAAM,SAAS,CAAC;;;;;;;AAyCtE,kBA6IE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
3
|
+
const node_path_1 = require("node:path");
|
|
4
|
+
const createRule = utils_1.ESLintUtils.RuleCreator(() => 'https://github.com/ekwoka/eslint-plugin-filename-export');
|
|
5
|
+
/** Metro / React Native platform suffixes (e.g. `Foo.web.tsx` → `Foo`). */
|
|
6
|
+
const PLATFORM_SUBEXTENSION = /\.(web|native|ios|android|windows|macos)$/;
|
|
7
|
+
const filenameBaseName = (filename) => {
|
|
8
|
+
let name = (0, node_path_1.basename)(filename, (0, node_path_1.extname)(filename));
|
|
9
|
+
while (PLATFORM_SUBEXTENSION.test(name)) {
|
|
10
|
+
name = name.replace(PLATFORM_SUBEXTENSION, '');
|
|
11
|
+
}
|
|
12
|
+
return name;
|
|
13
|
+
};
|
|
14
|
+
/** Dot-separated filename parts after stripping the language extension (e.g. `static.db.d`). */
|
|
15
|
+
const filenameSegments = (filename) => filenameBaseName(filename).split('.').filter(Boolean);
|
|
16
|
+
/**
|
|
17
|
+
* Export names that may match a dotted filename such as `static.db.d.ts`:
|
|
18
|
+
* full basename, each segment (`db`), and progressive camelCase (`staticDb`, `staticDbD`, …).
|
|
19
|
+
*/
|
|
20
|
+
const filenameExportNameCandidates = (filename) => {
|
|
21
|
+
const base = filenameBaseName(filename);
|
|
22
|
+
const segments = filenameSegments(filename);
|
|
23
|
+
const candidates = new Set([base, ...segments]);
|
|
24
|
+
if (segments.length > 0) {
|
|
25
|
+
let camel = segments[0];
|
|
26
|
+
candidates.add(camel);
|
|
27
|
+
for (let i = 1; i < segments.length; i++) {
|
|
28
|
+
const segment = segments[i];
|
|
29
|
+
camel += segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
30
|
+
candidates.add(camel);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [...candidates];
|
|
34
|
+
};
|
|
35
|
+
const getName = (exported) => {
|
|
36
|
+
if ('name' in exported)
|
|
37
|
+
return exported.name;
|
|
38
|
+
if ('id' in exported && exported.id)
|
|
39
|
+
if ('name' in exported.id)
|
|
40
|
+
return exported.id.name;
|
|
41
|
+
else if ('value' in exported.id)
|
|
42
|
+
return exported.id.value;
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
const getExportedNames = (exported) => {
|
|
46
|
+
if (exported.declaration)
|
|
47
|
+
return getNamesFromDeclarations(exported.declaration);
|
|
48
|
+
if (exported.specifiers)
|
|
49
|
+
return getNamesFromSpecifiers(exported.specifiers);
|
|
50
|
+
return [];
|
|
51
|
+
};
|
|
52
|
+
const getNamesFromDeclarations = (declaration) => {
|
|
53
|
+
var _a;
|
|
54
|
+
if ('declarations' in declaration && declaration.declarations)
|
|
55
|
+
return declaration.declarations.map((declaration) => { var _a; return (_a = getName(declaration)) !== null && _a !== void 0 ? _a : ''; });
|
|
56
|
+
return [(_a = getName(declaration)) !== null && _a !== void 0 ? _a : ''];
|
|
57
|
+
};
|
|
58
|
+
const getNamesFromSpecifiers = (specifiers) => specifiers.map((specifier) => {
|
|
59
|
+
if ('name' in specifier.exported)
|
|
60
|
+
return specifier.exported.name;
|
|
61
|
+
return specifier.exported.value;
|
|
62
|
+
});
|
|
63
|
+
const transformName = (name, transformers) => transformers.reduce((acc, fn) => fn(acc), name);
|
|
64
|
+
const compareExportToFilename = (exportName, filename, transformers) => {
|
|
65
|
+
const transformedExport = transformName(exportName, transformers);
|
|
66
|
+
return filenameExportNameCandidates(filename).some((candidate) => transformName(candidate, transformers) === transformedExport);
|
|
67
|
+
};
|
|
68
|
+
const makeTransformers = (options) => {
|
|
69
|
+
const transformers = [];
|
|
70
|
+
if (options.stripextra)
|
|
71
|
+
transformers.push((name) => name.replace(/[^a-zA-Z0-9]/g, ''));
|
|
72
|
+
if (options.casing !== 'strict')
|
|
73
|
+
transformers.push((name) => name.toLowerCase());
|
|
74
|
+
return transformers;
|
|
75
|
+
};
|
|
76
|
+
module.exports = {
|
|
77
|
+
rules: {
|
|
78
|
+
'match-named-export': createRule({
|
|
79
|
+
name: 'match-named-export',
|
|
80
|
+
defaultOptions: [
|
|
81
|
+
{
|
|
82
|
+
stripextra: false,
|
|
83
|
+
casing: 'loose',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
meta: {
|
|
87
|
+
docs: {
|
|
88
|
+
description: 'Enforce filename matches named export',
|
|
89
|
+
},
|
|
90
|
+
messages: {
|
|
91
|
+
noMatchingExport: 'Filename does not match any named exports',
|
|
92
|
+
},
|
|
93
|
+
type: 'suggestion',
|
|
94
|
+
schema: [
|
|
95
|
+
{
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
stripextra: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
},
|
|
101
|
+
casing: {
|
|
102
|
+
enum: ['strict', 'loose'],
|
|
103
|
+
type: 'string',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
create(context) {
|
|
110
|
+
var _a;
|
|
111
|
+
const transformers = makeTransformers((_a = context.options[0]) !== null && _a !== void 0 ? _a : {});
|
|
112
|
+
return {
|
|
113
|
+
Program(node) {
|
|
114
|
+
const filename = context.getFilename();
|
|
115
|
+
const filenameSansExt = filenameBaseName(filename);
|
|
116
|
+
if (['index', 'types'].includes(filenameSansExt) ||
|
|
117
|
+
/\.(test|spec|stories)$/.test(filenameSansExt))
|
|
118
|
+
return;
|
|
119
|
+
if (node.body.find((item) => item.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration))
|
|
120
|
+
return;
|
|
121
|
+
const namedExports = node.body.filter((item) => item.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration);
|
|
122
|
+
if (!namedExports.length)
|
|
123
|
+
return;
|
|
124
|
+
const exportedNames = namedExports.flatMap((exp) => getExportedNames(exp));
|
|
125
|
+
// Only enforce filename ↔ export name when the file has a single named export.
|
|
126
|
+
if (exportedNames.length !== 1)
|
|
127
|
+
return;
|
|
128
|
+
const matchingExport = exportedNames.some((name) => compareExportToFilename(name, filename, transformers));
|
|
129
|
+
if (!matchingExport)
|
|
130
|
+
context.report({
|
|
131
|
+
messageId: 'noMatchingExport',
|
|
132
|
+
node,
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
'match-default-export': createRule({
|
|
139
|
+
name: 'match-default-export',
|
|
140
|
+
defaultOptions: [
|
|
141
|
+
{
|
|
142
|
+
stripextra: false,
|
|
143
|
+
casing: 'loose',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
meta: {
|
|
147
|
+
docs: {
|
|
148
|
+
description: 'Enforce filename matches named export',
|
|
149
|
+
},
|
|
150
|
+
messages: {
|
|
151
|
+
defaultExportDoesNotMatch: 'Filename does not match the default export',
|
|
152
|
+
},
|
|
153
|
+
type: 'suggestion',
|
|
154
|
+
schema: [
|
|
155
|
+
{
|
|
156
|
+
type: 'object',
|
|
157
|
+
properties: {
|
|
158
|
+
stripextra: {
|
|
159
|
+
type: 'boolean',
|
|
160
|
+
},
|
|
161
|
+
casing: {
|
|
162
|
+
enum: ['strict', 'loose'],
|
|
163
|
+
type: 'string',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
create(context) {
|
|
170
|
+
var _a;
|
|
171
|
+
const transformers = makeTransformers((_a = context.options[0]) !== null && _a !== void 0 ? _a : {});
|
|
172
|
+
return {
|
|
173
|
+
Program(node) {
|
|
174
|
+
const filename = context.getFilename();
|
|
175
|
+
const filenameSansExt = filenameBaseName(filename);
|
|
176
|
+
if (['index', 'types'].includes(filenameSansExt) ||
|
|
177
|
+
/\.(test|spec|stories)$/.test(filenameSansExt))
|
|
178
|
+
return;
|
|
179
|
+
const defaultExport = node.body.find((item) => item.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration);
|
|
180
|
+
if (!defaultExport)
|
|
181
|
+
return;
|
|
182
|
+
const declaration = defaultExport.declaration;
|
|
183
|
+
if (!('name' in declaration || 'id' in declaration))
|
|
184
|
+
return;
|
|
185
|
+
const defaultName = getName(declaration);
|
|
186
|
+
if (!defaultName)
|
|
187
|
+
return;
|
|
188
|
+
const isMatching = compareExportToFilename(defaultName, filename, transformers);
|
|
189
|
+
if (!isMatching)
|
|
190
|
+
context.report({
|
|
191
|
+
messageId: 'defaultExportDoesNotMatch',
|
|
192
|
+
node,
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"src/","sources":["index.ts"],"names":[],"mappings":";AAAA,oDAIkC;AAElC,yCAA8C;AAI9C,MAAM,UAAU,GAAG,mBAAW,CAAC,WAAW,CACxC,GAAG,EAAE,CAAC,yDAAyD,CAChE,CAAC;AAEF,2EAA2E;AAC3E,MAAM,qBAAqB,GAAG,2CAA2C,CAAC;AAE1E,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAE,EAAE;IAC5C,IAAI,IAAI,GAAG,IAAA,oBAAQ,EAAC,QAAQ,EAAE,IAAA,mBAAO,EAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,gGAAgG;AAChG,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAE,EAAE,CAC5C,gBAAgB,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,4BAA4B,GAAG,CAAC,QAAgB,EAAE,EAAE;IACxD,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,CAAC,IAAI,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;IACxD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,IAAI,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxB,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC5B,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5D,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,UAAU,CAAC,CAAC;AACzB,CAAC,CAAC;AAiJF,MAAM,OAAO,GAAG,CACd,QAAkE,EACnD,EAAE;IACjB,IAAI,MAAM,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,IAAI,CAAC;IAC7C,IAAI,IAAI,IAAI,QAAQ,IAAI,QAAQ,CAAC,EAAE;QACjC,IAAI,MAAM,IAAI,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC;aAC9C,IAAI,OAAO,IAAI,QAAQ,CAAC,EAAE;YAAE,OAAO,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,QAAyC,EAAE,EAAE;IACrE,IAAI,QAAQ,CAAC,WAAW;QACtB,OAAO,wBAAwB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxD,IAAI,QAAQ,CAAC,UAAU;QAAE,OAAO,sBAAsB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC5E,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAC/B,WAA6C,EACnC,EAAE;;IACZ,IAAI,cAAc,IAAI,WAAW,IAAI,WAAW,CAAC,YAAY;QAC3D,OAAO,WAAW,CAAC,YAAY,CAAC,GAAG,CACjC,CAAC,WAAW,EAAE,EAAE,WAAC,OAAA,MAAA,OAAO,CAAC,WAAW,CAAC,mCAAI,EAAE,CAAA,EAAA,CAC5C,CAAC;IACJ,OAAO,CAAC,MAAA,OAAO,CAAC,WAAW,CAAC,mCAAI,EAAE,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAC7B,UAAsC,EAC5B,EAAE,CACZ,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;IAC3B,IAAI,MAAM,IAAI,SAAS,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;IACjE,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;AAClC,CAAC,CAAC,CAAC;AAEL,MAAM,aAAa,GAAG,CACpB,IAAY,EACZ,YAA0C,EAC1C,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AAErD,MAAM,uBAAuB,GAAG,CAC9B,UAAkB,EAClB,QAAgB,EAChB,YAA0C,EAC1C,EAAE;IACF,MAAM,iBAAiB,GAAG,aAAa,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAClE,OAAO,4BAA4B,CAAC,QAAQ,CAAC,CAAC,IAAI,CAChD,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,YAAY,CAAC,KAAK,iBAAiB,CAC5E,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,OAAmB,EAAE,EAAE;IAC/C,MAAM,YAAY,GAAiC,EAAE,CAAC;IACtD,IAAI,OAAO,CAAC,UAAU;QACpB,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;IACjE,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ;QAC7B,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAClD,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AAzMF,iBAAS;IACP,KAAK,EAAE;QACL,oBAAoB,EAAE,UAAU,CAA2B;YACzD,IAAI,EAAE,oBAAoB;YAC1B,cAAc,EAAE;gBACd;oBACE,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,OAAO;iBAChB;aACF;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE;oBACJ,WAAW,EAAE,uCAAuC;iBACrD;gBACD,QAAQ,EAAE;oBACR,gBAAgB,EAAE,2CAA2C;iBAC9D;gBACD,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,UAAU,EAAE;gCACV,IAAI,EAAE,SAAS;6BAChB;4BACD,MAAM,EAAE;gCACN,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;gCACzB,IAAI,EAAE,QAAQ;6BACf;yBACF;qBACF;iBACF;aACF;YACD,MAAM,CAAC,OAAO;;gBACZ,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAA,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAC,CAAC;gBAChE,OAAO;oBACL,OAAO,CAAC,IAAI;wBACV,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;wBACvC,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;wBACnD,IACE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;4BAC5C,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC;4BAE9C,OAAO;wBACT,IACE,IAAI,CAAC,IAAI,CAAC,IAAI,CACZ,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,wBAAwB,CAChE;4BAED,OAAO;wBACT,MAAM,YAAY,GAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CACd,CAAC,IAAI,EAA2C,EAAE,CAChD,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,sBAAsB,CACtD,CAAC;wBACJ,IAAI,CAAC,YAAY,CAAC,MAAM;4BAAE,OAAO;wBACjC,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACjD,gBAAgB,CAAC,GAAG,CAAC,CACtB,CAAC;wBACF,+EAA+E;wBAC/E,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;4BAAE,OAAO;wBACvC,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CACjD,uBAAuB,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,CACtD,CAAC;wBACF,IAAI,CAAC,cAAc;4BACjB,OAAO,CAAC,MAAM,CAAC;gCACb,SAAS,EAAE,kBAAkB;gCAC7B,IAAI;6BACL,CAAC,CAAC;oBACP,CAAC;iBACF,CAAC;YACJ,CAAC;SACF,CAAC;QACF,sBAAsB,EAAE,UAAU,CAA6B;YAC7D,IAAI,EAAE,sBAAsB;YAC5B,cAAc,EAAE;gBACd;oBACE,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,OAAO;iBAChB;aACF;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE;oBACJ,WAAW,EAAE,uCAAuC;iBACrD;gBACD,QAAQ,EAAE;oBACR,yBAAyB,EACvB,4CAA4C;iBAC/C;gBACD,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE;oBACN;wBACE,IAAI,EAAE,QAAQ;wBACd,UAAU,EAAE;4BACV,UAAU,EAAE;gCACV,IAAI,EAAE,SAAS;6BAChB;4BACD,MAAM,EAAE;gCACN,IAAI,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;gCACzB,IAAI,EAAE,QAAQ;6BACf;yBACF;qBACF;iBACF;aACF;YACD,MAAM,CAAC,OAAO;;gBACZ,MAAM,YAAY,GAAG,gBAAgB,CAAC,MAAA,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAC,CAAC;gBAChE,OAAO;oBACL,OAAO,CAAC,IAAI;wBACV,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;wBACvC,MAAM,eAAe,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;wBACnD,IACE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;4BAC5C,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC;4BAE9C,OAAO;wBAET,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAClC,CAAC,IAAI,EAA6C,EAAE,CAClD,IAAI,CAAC,IAAI,KAAK,sBAAc,CAAC,wBAAwB,CACxD,CAAC;wBACF,IAAI,CAAC,aAAa;4BAAE,OAAO;wBAC3B,MAAM,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;wBAC9C,IAAI,CAAC,CAAC,MAAM,IAAI,WAAW,IAAI,IAAI,IAAI,WAAW,CAAC;4BAAE,OAAO;wBAC5D,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;wBACzC,IAAI,CAAC,WAAW;4BAAE,OAAO;wBACzB,MAAM,UAAU,GAAG,uBAAuB,CACxC,WAAW,EACX,QAAQ,EACR,YAAY,CACb,CAAC;wBACF,IAAI,CAAC,UAAU;4BACb,OAAO,CAAC,MAAM,CAAC;gCACb,SAAS,EAAE,2BAA2B;gCACtC,IAAI;6BACL,CAAC,CAAC;oBACP,CAAC;iBACF,CAAC;YACJ,CAAC;SACF,CAAC;KACH;CACF,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"src/","sources":["types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AACjD,MAAM,MAAM,iBAAiB,GAAG,2BAA2B,CAAC;AAC5D,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,MAAM,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;KAC7B;CACF,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"src/","sources":["types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leonsilicon/eslint-plugin-filename-export",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "ESLint plugin that ensures non-index filenames match to at least one named export.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"coverage": "vitest run --coverage",
|
|
14
|
+
"lint": "eslint --fix ./src; prettier --write ./src --loglevel error",
|
|
15
|
+
"lint:check": "eslint --max-warnings 10 ./src && prettier --check ./src",
|
|
16
|
+
"prebuild": "rm -rf dist",
|
|
17
|
+
"prepare": "husky install",
|
|
18
|
+
"size": "node scripts/esbuild.js",
|
|
19
|
+
"test": "vitest",
|
|
20
|
+
"test:run": "vitest --run",
|
|
21
|
+
"test:verbose": "vitest --reporter=verbose"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ESLint",
|
|
25
|
+
"esm",
|
|
26
|
+
"export",
|
|
27
|
+
"filename"
|
|
28
|
+
],
|
|
29
|
+
"author": {
|
|
30
|
+
"name": "Eric Kwoka",
|
|
31
|
+
"email": "ninjatheory@gmail.com",
|
|
32
|
+
"url": "https://thekwoka.net/"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": "github:ekwoka/eslint-plugin-filename-export",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/ekwoka/eslint-plugin-filename-export/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/ekwoka/eslint-plugin-filename-export",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@typescript-eslint/utils": "^8.38.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@trivago/prettier-plugin-sort-imports": "4.3.0",
|
|
48
|
+
"@types/node": "20.19.39",
|
|
49
|
+
"@typescript-eslint/eslint-plugin": "6.21.0",
|
|
50
|
+
"@typescript-eslint/parser": "6.21.0",
|
|
51
|
+
"@typescript-eslint/rule-tester": "8.59.1",
|
|
52
|
+
"@vitest/coverage-c8": "0.33.0",
|
|
53
|
+
"esbuild": "0.28.0",
|
|
54
|
+
"eslint": "8.57.1",
|
|
55
|
+
"eslint-plugin-filename-export": "1.0.4",
|
|
56
|
+
"husky": "8.0.3",
|
|
57
|
+
"lint-staged": "14.0.1",
|
|
58
|
+
"prettier": "3.8.3",
|
|
59
|
+
"typescript": "5.9.3",
|
|
60
|
+
"vite": "4.5.14",
|
|
61
|
+
"vite-tsconfig-paths": "4.3.2",
|
|
62
|
+
"vitest": "0.34.6"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
},
|
|
67
|
+
"prettier": {
|
|
68
|
+
"singleQuote": true,
|
|
69
|
+
"bracketSameLine": true,
|
|
70
|
+
"plugins": [
|
|
71
|
+
"@trivago/prettier-plugin-sort-imports"
|
|
72
|
+
],
|
|
73
|
+
"importOrder": [
|
|
74
|
+
"node:.*",
|
|
75
|
+
"@/lib(.*)$",
|
|
76
|
+
"@/utils(.*)$",
|
|
77
|
+
"^[./]"
|
|
78
|
+
],
|
|
79
|
+
"importOrderSeparation": true,
|
|
80
|
+
"importOrderSortSpecifiers": true
|
|
81
|
+
},
|
|
82
|
+
"lint-staged": {
|
|
83
|
+
"*.{js,ts,mjs}": [
|
|
84
|
+
"eslint --fix",
|
|
85
|
+
"prettier --write"
|
|
86
|
+
],
|
|
87
|
+
"*.json": [
|
|
88
|
+
"prettier --write"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"pnpm": {
|
|
92
|
+
"onlyBuiltDependencies": [
|
|
93
|
+
"esbuild"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"packageManager": "pnpm@11.5.3"
|
|
97
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AST_NODE_TYPES,
|
|
3
|
+
ESLintUtils,
|
|
4
|
+
TSESTree,
|
|
5
|
+
} from '@typescript-eslint/utils';
|
|
6
|
+
|
|
7
|
+
import { basename, extname } from 'node:path';
|
|
8
|
+
|
|
9
|
+
import { DefaultMessageIds, NamedMessageIds, Options } from './types';
|
|
10
|
+
|
|
11
|
+
const createRule = ESLintUtils.RuleCreator(
|
|
12
|
+
() => 'https://github.com/ekwoka/eslint-plugin-filename-export',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
/** Metro / React Native platform suffixes (e.g. `Foo.web.tsx` → `Foo`). */
|
|
16
|
+
const PLATFORM_SUBEXTENSION = /\.(web|native|ios|android|windows|macos)$/;
|
|
17
|
+
|
|
18
|
+
const filenameBaseName = (filename: string) => {
|
|
19
|
+
let name = basename(filename, extname(filename));
|
|
20
|
+
while (PLATFORM_SUBEXTENSION.test(name)) {
|
|
21
|
+
name = name.replace(PLATFORM_SUBEXTENSION, '');
|
|
22
|
+
}
|
|
23
|
+
return name;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Dot-separated filename parts after stripping the language extension (e.g. `static.db.d`). */
|
|
27
|
+
const filenameSegments = (filename: string) =>
|
|
28
|
+
filenameBaseName(filename).split('.').filter(Boolean);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Export names that may match a dotted filename such as `static.db.d.ts`:
|
|
32
|
+
* full basename, each segment (`db`), and progressive camelCase (`staticDb`, `staticDbD`, …).
|
|
33
|
+
*/
|
|
34
|
+
const filenameExportNameCandidates = (filename: string) => {
|
|
35
|
+
const base = filenameBaseName(filename);
|
|
36
|
+
const segments = filenameSegments(filename);
|
|
37
|
+
const candidates = new Set<string>([base, ...segments]);
|
|
38
|
+
if (segments.length > 0) {
|
|
39
|
+
let camel = segments[0];
|
|
40
|
+
candidates.add(camel);
|
|
41
|
+
for (let i = 1; i < segments.length; i++) {
|
|
42
|
+
const segment = segments[i];
|
|
43
|
+
camel += segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
44
|
+
candidates.add(camel);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [...candidates];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export = {
|
|
51
|
+
rules: {
|
|
52
|
+
'match-named-export': createRule<Options, NamedMessageIds>({
|
|
53
|
+
name: 'match-named-export',
|
|
54
|
+
defaultOptions: [
|
|
55
|
+
{
|
|
56
|
+
stripextra: false,
|
|
57
|
+
casing: 'loose',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
meta: {
|
|
61
|
+
docs: {
|
|
62
|
+
description: 'Enforce filename matches named export',
|
|
63
|
+
},
|
|
64
|
+
messages: {
|
|
65
|
+
noMatchingExport: 'Filename does not match any named exports',
|
|
66
|
+
},
|
|
67
|
+
type: 'suggestion',
|
|
68
|
+
schema: [
|
|
69
|
+
{
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
stripextra: {
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
},
|
|
75
|
+
casing: {
|
|
76
|
+
enum: ['strict', 'loose'],
|
|
77
|
+
type: 'string',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
create(context) {
|
|
84
|
+
const transformers = makeTransformers(context.options[0] ?? {});
|
|
85
|
+
return {
|
|
86
|
+
Program(node) {
|
|
87
|
+
const filename = context.getFilename();
|
|
88
|
+
const filenameSansExt = filenameBaseName(filename);
|
|
89
|
+
if (
|
|
90
|
+
['index', 'types'].includes(filenameSansExt) ||
|
|
91
|
+
/\.(test|spec|stories)$/.test(filenameSansExt)
|
|
92
|
+
)
|
|
93
|
+
return;
|
|
94
|
+
if (
|
|
95
|
+
node.body.find(
|
|
96
|
+
(item) => item.type === AST_NODE_TYPES.ExportDefaultDeclaration,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
return;
|
|
100
|
+
const namedExports =
|
|
101
|
+
node.body.filter<TSESTree.ExportNamedDeclaration>(
|
|
102
|
+
(item): item is TSESTree.ExportNamedDeclaration =>
|
|
103
|
+
item.type === AST_NODE_TYPES.ExportNamedDeclaration,
|
|
104
|
+
);
|
|
105
|
+
if (!namedExports.length) return;
|
|
106
|
+
const exportedNames = namedExports.flatMap((exp) =>
|
|
107
|
+
getExportedNames(exp),
|
|
108
|
+
);
|
|
109
|
+
// Only enforce filename ↔ export name when the file has a single named export.
|
|
110
|
+
if (exportedNames.length !== 1) return;
|
|
111
|
+
const matchingExport = exportedNames.some((name) =>
|
|
112
|
+
compareExportToFilename(name, filename, transformers),
|
|
113
|
+
);
|
|
114
|
+
if (!matchingExport)
|
|
115
|
+
context.report({
|
|
116
|
+
messageId: 'noMatchingExport',
|
|
117
|
+
node,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
'match-default-export': createRule<Options, DefaultMessageIds>({
|
|
124
|
+
name: 'match-default-export',
|
|
125
|
+
defaultOptions: [
|
|
126
|
+
{
|
|
127
|
+
stripextra: false,
|
|
128
|
+
casing: 'loose',
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
meta: {
|
|
132
|
+
docs: {
|
|
133
|
+
description: 'Enforce filename matches named export',
|
|
134
|
+
},
|
|
135
|
+
messages: {
|
|
136
|
+
defaultExportDoesNotMatch:
|
|
137
|
+
'Filename does not match the default export',
|
|
138
|
+
},
|
|
139
|
+
type: 'suggestion',
|
|
140
|
+
schema: [
|
|
141
|
+
{
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
stripextra: {
|
|
145
|
+
type: 'boolean',
|
|
146
|
+
},
|
|
147
|
+
casing: {
|
|
148
|
+
enum: ['strict', 'loose'],
|
|
149
|
+
type: 'string',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
create(context) {
|
|
156
|
+
const transformers = makeTransformers(context.options[0] ?? {});
|
|
157
|
+
return {
|
|
158
|
+
Program(node) {
|
|
159
|
+
const filename = context.getFilename();
|
|
160
|
+
const filenameSansExt = filenameBaseName(filename);
|
|
161
|
+
if (
|
|
162
|
+
['index', 'types'].includes(filenameSansExt) ||
|
|
163
|
+
/\.(test|spec|stories)$/.test(filenameSansExt)
|
|
164
|
+
)
|
|
165
|
+
return;
|
|
166
|
+
|
|
167
|
+
const defaultExport = node.body.find(
|
|
168
|
+
(item): item is TSESTree.ExportDefaultDeclaration =>
|
|
169
|
+
item.type === AST_NODE_TYPES.ExportDefaultDeclaration,
|
|
170
|
+
);
|
|
171
|
+
if (!defaultExport) return;
|
|
172
|
+
const declaration = defaultExport.declaration;
|
|
173
|
+
if (!('name' in declaration || 'id' in declaration)) return;
|
|
174
|
+
const defaultName = getName(declaration);
|
|
175
|
+
if (!defaultName) return;
|
|
176
|
+
const isMatching = compareExportToFilename(
|
|
177
|
+
defaultName,
|
|
178
|
+
filename,
|
|
179
|
+
transformers,
|
|
180
|
+
);
|
|
181
|
+
if (!isMatching)
|
|
182
|
+
context.report({
|
|
183
|
+
messageId: 'defaultExportDoesNotMatch',
|
|
184
|
+
node,
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const getName = (
|
|
194
|
+
exported: TSESTree.ExportDeclaration | TSESTree.VariableDeclarator,
|
|
195
|
+
): string | null => {
|
|
196
|
+
if ('name' in exported) return exported.name;
|
|
197
|
+
if ('id' in exported && exported.id)
|
|
198
|
+
if ('name' in exported.id) return exported.id.name;
|
|
199
|
+
else if ('value' in exported.id) return exported.id.value;
|
|
200
|
+
return null;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const getExportedNames = (exported: TSESTree.ExportNamedDeclaration) => {
|
|
204
|
+
if (exported.declaration)
|
|
205
|
+
return getNamesFromDeclarations(exported.declaration);
|
|
206
|
+
if (exported.specifiers) return getNamesFromSpecifiers(exported.specifiers);
|
|
207
|
+
return [];
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const getNamesFromDeclarations = (
|
|
211
|
+
declaration: TSESTree.NamedExportDeclarations,
|
|
212
|
+
): string[] => {
|
|
213
|
+
if ('declarations' in declaration && declaration.declarations)
|
|
214
|
+
return declaration.declarations.map(
|
|
215
|
+
(declaration) => getName(declaration) ?? '',
|
|
216
|
+
);
|
|
217
|
+
return [getName(declaration) ?? ''];
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const getNamesFromSpecifiers = (
|
|
221
|
+
specifiers: TSESTree.ExportSpecifier[],
|
|
222
|
+
): string[] =>
|
|
223
|
+
specifiers.map((specifier) => {
|
|
224
|
+
if ('name' in specifier.exported) return specifier.exported.name;
|
|
225
|
+
return specifier.exported.value;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const transformName = (
|
|
229
|
+
name: string,
|
|
230
|
+
transformers: ((name: string) => string)[],
|
|
231
|
+
) => transformers.reduce((acc, fn) => fn(acc), name);
|
|
232
|
+
|
|
233
|
+
const compareExportToFilename = (
|
|
234
|
+
exportName: string,
|
|
235
|
+
filename: string,
|
|
236
|
+
transformers: ((name: string) => string)[],
|
|
237
|
+
) => {
|
|
238
|
+
const transformedExport = transformName(exportName, transformers);
|
|
239
|
+
return filenameExportNameCandidates(filename).some(
|
|
240
|
+
(candidate) => transformName(candidate, transformers) === transformedExport,
|
|
241
|
+
);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const makeTransformers = (options: Options[0]) => {
|
|
245
|
+
const transformers: ((name: string) => string)[] = [];
|
|
246
|
+
if (options.stripextra)
|
|
247
|
+
transformers.push((name) => name.replace(/[^a-zA-Z0-9]/g, ''));
|
|
248
|
+
if (options.casing !== 'strict')
|
|
249
|
+
transformers.push((name) => name.toLowerCase());
|
|
250
|
+
return transformers;
|
|
251
|
+
};
|