From 5d28d26cbcb4b8c0b47f0a572c0702787d2b6248 Mon Sep 17 00:00:00 2001 From: Michael J Kim Date: Tue, 18 Jul 2023 22:40:21 -0400 Subject: [PATCH 1/2] [naming-convention] add support for default and namespace imports --- .../docs/rules/naming-convention.md | 3 + .../rules/naming-convention-utils/enums.ts | 17 +++-- .../rules/naming-convention-utils/schema.ts | 1 + .../src/rules/naming-convention.ts | 35 +++++++++ .../naming-convention.test.ts | 69 +++++++++++++++++ .../schema-snapshots/naming-convention.shot | 75 +++++++++++++++++++ 6 files changed, 195 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/naming-convention.md b/packages/eslint-plugin/docs/rules/naming-convention.md index 53c381c9e74f..25c3a5c12bf1 100644 --- a/packages/eslint-plugin/docs/rules/naming-convention.md +++ b/packages/eslint-plugin/docs/rules/naming-convention.md @@ -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. diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts index 1b26c18ecb22..b92928b86477 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/enums.ts @@ -43,6 +43,9 @@ enum Selectors { typeAlias = 1 << 14, enum = 1 << 15, typeParameter = 1 << 17, + + // other + import = 1 << 18, } type SelectorsString = keyof typeof Selectors; @@ -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; diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts index 2cb6d9fed62b..fdbbd5821ad2 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/schema.ts @@ -310,6 +310,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, diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index d3e52d377cf2..0a8aab5e976e 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -221,6 +221,41 @@ export default util.createRule({ ) => void; }>; } = { + // #region import + + 'ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier': { + validator: validators.import, + handler: ( + node: + | TSESTree.ImportDefaultSpecifier + | TSESTree.ImportNamespaceSpecifier + | TSESTree.ImportSpecifier, + validator, + ): void => { + const modifiers = new Set(); + + 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: { diff --git a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts index 75156ebb2ce7..ce45dbd33081 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts @@ -932,6 +932,26 @@ ruleTester.run('naming-convention', rule, { }, ], }, + { + code: ` + import * as FooBar from 'foo_bar'; + import fooBar from 'foo_bar'; + import { default as fooBar } from 'foo_bar'; + import { foo_bar } from 'foo_bar'; + `, + parserOptions, + options: [ + { + selector: ['import'], + format: ['PascalCase'], + }, + { + selector: ['import'], + modifiers: ['default'], + format: ['camelCase'], + }, + ], + }, ], invalid: [ { @@ -2121,5 +2141,54 @@ ruleTester.run('naming-convention', rule, { }, ], }, + { + code: ` + // ❌ error + import * as fooBar from 'foo_bar'; + // ❌ error + import FooBar from 'foo_bar'; + // ❌ error + import { default as foo_bar } from 'foo_bar'; + import { foo_bar } 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', + }, + }, + { + messageId: 'doesNotMatchFormat', + data: { + type: 'Import', + name: 'FooBar', + formats: 'camelCase', + }, + }, + { + messageId: 'doesNotMatchFormat', + data: { + type: 'Import', + name: 'foo_bar', + formats: 'camelCase', + }, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot b/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot index 1f360bc55c29..8ae5a4a339d9 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/naming-convention.shot @@ -106,9 +106,11 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "abstract", "async", "const", + "default", "destructured", "exported", "global", + "namespace", "override", "private", "protected", @@ -137,6 +139,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "enum", "enumMember", "function", + "import", "interface", "memberLike", "method", @@ -209,9 +212,11 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "abstract", "async", "const", + "default", "destructured", "exported", "global", + "namespace", "override", "private", "protected", @@ -1508,6 +1513,58 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos }, "required": ["selector", "format"], "type": "object" + }, + { + "additionalProperties": false, + "description": "Selector 'import'", + "properties": { + "custom": { + "$ref": "#/$defs/matchRegexConfig" + }, + "failureMessage": { + "type": "string" + }, + "filter": { + "oneOf": [ + { + "minLength": 1, + "type": "string" + }, + { + "$ref": "#/$defs/matchRegexConfig" + } + ] + }, + "format": { + "$ref": "#/$defs/formatOptionsConfig" + }, + "leadingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + }, + "modifiers": { + "additionalItems": false, + "items": { + "enum": ["default", "namespace"], + "type": "string" + }, + "type": "array" + }, + "prefix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "selector": { + "enum": ["import"], + "type": "string" + }, + "suffix": { + "$ref": "#/$defs/prefixSuffixConfig" + }, + "trailingUnderscore": { + "$ref": "#/$defs/underscoreOptions" + } + }, + "required": ["selector", "format"], + "type": "object" } ] }, @@ -1556,9 +1613,11 @@ type Options = /** Multiple selectors in one config */ | 'abstract' | 'async' | 'const' + | 'default' | 'destructured' | 'exported' | 'global' + | 'namespace' | 'override' | 'private' | 'protected' @@ -1578,6 +1637,7 @@ type Options = /** Multiple selectors in one config */ | 'enum' | 'enumMember' | 'function' + | 'import' | 'interface' | 'memberLike' | 'method' @@ -1692,9 +1752,11 @@ type Options = /** Multiple selectors in one config */ | 'abstract' | 'async' | 'const' + | 'default' | 'destructured' | 'exported' | 'global' + | 'namespace' | 'override' | 'private' | 'protected' @@ -1748,6 +1810,19 @@ type Options = /** Multiple selectors in one config */ suffix?: PrefixSuffixConfig; trailingUnderscore?: UnderscoreOptions; } + /** Selector 'import' */ + | { + custom?: MatchRegexConfig; + failureMessage?: string; + filter?: MatchRegexConfig | string; + format: FormatOptionsConfig; + leadingUnderscore?: UnderscoreOptions; + modifiers?: ('default' | 'namespace')[]; + prefix?: PrefixSuffixConfig; + selector: 'import'; + suffix?: PrefixSuffixConfig; + trailingUnderscore?: UnderscoreOptions; + } /** Selector 'interface' */ | { custom?: MatchRegexConfig; From bc9253356276ae5b01b02afd5d7f62fe0231bea2 Mon Sep 17 00:00:00 2001 From: Michael J Kim Date: Mon, 7 Aug 2023 21:45:47 -0400 Subject: [PATCH 2/2] split tests --- .../naming-convention.test.ts | 96 ++++++++++++++++--- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts index ce45dbd33081..731fc22d9c42 100644 --- a/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts +++ b/packages/eslint-plugin/tests/rules/naming-convention/naming-convention.test.ts @@ -933,12 +933,52 @@ ruleTester.run('naming-convention', rule, { ], }, { - code: ` - import * as FooBar from 'foo_bar'; - import fooBar from 'foo_bar'; - import { default as fooBar } from 'foo_bar'; - import { foo_bar } from 'foo_bar'; - `, + 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: [ { @@ -2142,15 +2182,7 @@ ruleTester.run('naming-convention', rule, { ], }, { - code: ` - // ❌ error - import * as fooBar from 'foo_bar'; - // ❌ error - import FooBar from 'foo_bar'; - // ❌ error - import { default as foo_bar } from 'foo_bar'; - import { foo_bar } from 'foo_bar'; - `, + code: "import * as fooBar from 'foo_bar';", parserOptions, options: [ { @@ -2172,6 +2204,23 @@ ruleTester.run('naming-convention', rule, { formats: 'PascalCase', }, }, + ], + }, + { + code: "import FooBar from 'foo_bar';", + parserOptions, + options: [ + { + selector: ['import'], + format: ['camelCase'], + }, + { + selector: ['import'], + modifiers: ['namespace'], + format: ['PascalCase'], + }, + ], + errors: [ { messageId: 'doesNotMatchFormat', data: { @@ -2180,6 +2229,23 @@ ruleTester.run('naming-convention', rule, { 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: { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy