Skip to content

Commit

Permalink
feat(eslint-plugin): [naming-convention] add support for default and …
Browse files Browse the repository at this point in the history
…namespace imports (#7269)

* [naming-convention] add support for default and namespace imports

* split tests
  • Loading branch information
binoche9 committed Oct 17, 2023
1 parent b85f744 commit bb15aae
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/eslint-plugin/docs/rules/naming-convention.md
Expand Up @@ -216,6 +216,9 @@ Individual Selectors match specific, well-defined sets. There is no overlap betw
- `function` - matches any named function declaration or named function expression.
- Allowed `modifiers`: `async`, `exported`, `global`, `unused`.
- Allowed `types`: none.
- `import` - matches namespace imports and default imports (i.e. does not match named imports).
- Allowed `modifiers`: `default`, `namespace`.
- Allowed `types`: none.
- `interface` - matches any interface declaration.
- Allowed `modifiers`: `exported`, `unused`.
- Allowed `types`: none.
Expand Down
17 changes: 12 additions & 5 deletions packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts
Expand Up @@ -43,6 +43,9 @@ enum Selectors {
typeAlias = 1 << 14,
enum = 1 << 15,
typeParameter = 1 << 17,

// other
import = 1 << 18,
}
type SelectorsString = keyof typeof Selectors;

Expand Down Expand Up @@ -107,17 +110,21 @@ enum Modifiers {
override = 1 << 13,
// class methods, object function properties, or functions that are async via the `async` keyword
async = 1 << 14,
// default imports
default = 1 << 15,
// namespace imports
namespace = 1 << 16,

// make sure TypeModifiers starts at Modifiers + 1 or else sorting won't work
}
type ModifiersString = keyof typeof Modifiers;

enum TypeModifiers {
boolean = 1 << 15,
string = 1 << 16,
number = 1 << 17,
function = 1 << 18,
array = 1 << 19,
boolean = 1 << 17,
string = 1 << 18,
number = 1 << 19,
function = 1 << 20,
array = 1 << 21,
}
type TypeModifiersString = keyof typeof TypeModifiers;

Expand Down
Expand Up @@ -307,6 +307,7 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
...selectorSchema('typeAlias', false, ['exported', 'unused']),
...selectorSchema('enum', false, ['exported', 'unused']),
...selectorSchema('typeParameter', false, ['unused']),
...selectorSchema('import', false, ['default', 'namespace']),
],
},
additionalItems: false,
Expand Down
35 changes: 35 additions & 0 deletions packages/eslint-plugin/src/rules/naming-convention.ts
Expand Up @@ -226,6 +226,41 @@ export default createRule<Options, MessageIds>({
) => void;
}>;
} = {
// #region import

'ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier': {
validator: validators.import,
handler: (
node:
| TSESTree.ImportDefaultSpecifier
| TSESTree.ImportNamespaceSpecifier
| TSESTree.ImportSpecifier,
validator,
): void => {
const modifiers = new Set<Modifiers>();

switch (node.type) {
case AST_NODE_TYPES.ImportDefaultSpecifier:
modifiers.add(Modifiers.default);
break;
case AST_NODE_TYPES.ImportNamespaceSpecifier:
modifiers.add(Modifiers.namespace);
break;
case AST_NODE_TYPES.ImportSpecifier:
// Handle `import { default as Foo }`
if (node.imported.name !== 'default') {
return;
}
modifiers.add(Modifiers.default);
break;
}

validator(node.local, modifiers);
},
},

// #endregion

// #region variable

VariableDeclarator: {
Expand Down
Expand Up @@ -932,6 +932,66 @@ ruleTester.run('naming-convention', rule, {
},
],
},
{
code: "import * as FooBar from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['PascalCase'],
},
{
selector: ['import'],
modifiers: ['default'],
format: ['camelCase'],
},
],
},
{
code: "import fooBar from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['PascalCase'],
},
{
selector: ['import'],
modifiers: ['default'],
format: ['camelCase'],
},
],
},
{
code: "import { default as fooBar } from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['PascalCase'],
},
{
selector: ['import'],
modifiers: ['default'],
format: ['camelCase'],
},
],
},
{
code: "import { foo_bar } from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['PascalCase'],
},
{
selector: ['import'],
modifiers: ['default'],
format: ['camelCase'],
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -2121,5 +2181,80 @@ ruleTester.run('naming-convention', rule, {
},
],
},
{
code: "import * as fooBar from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['camelCase'],
},
{
selector: ['import'],
modifiers: ['namespace'],
format: ['PascalCase'],
},
],
errors: [
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Import',
name: 'fooBar',
formats: 'PascalCase',
},
},
],
},
{
code: "import FooBar from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['camelCase'],
},
{
selector: ['import'],
modifiers: ['namespace'],
format: ['PascalCase'],
},
],
errors: [
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Import',
name: 'FooBar',
formats: 'camelCase',
},
},
],
},
{
code: "import { default as foo_bar } from 'foo_bar';",
parserOptions,
options: [
{
selector: ['import'],
format: ['camelCase'],
},
{
selector: ['import'],
modifiers: ['namespace'],
format: ['PascalCase'],
},
],
errors: [
{
messageId: 'doesNotMatchFormat',
data: {
type: 'Import',
name: 'foo_bar',
formats: 'camelCase',
},
},
],
},
],
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit bb15aae

Please sign in to comment.