Skip to content

Commit 7d124ee

Browse files
test: reduce failing tests
1 parent d84fbfa commit 7d124ee

File tree

2 files changed

+72
-62
lines changed

2 files changed

+72
-62
lines changed

README.md

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,14 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
6969
| [no-useless-path-segments](docs/rules/no-useless-path-segments.md) | Forbid unnecessary path segments in import and require statements. | | | | 🔧 | | |
7070
| [no-webpack-loader-syntax](docs/rules/no-webpack-loader-syntax.md) | Forbid webpack loader syntax in imports. | | | | | | |
7171

72-
### Static Analysis
73-
74-
| Name       | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 ||
75-
| :------------------------------------- | :--------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | :- |
76-
| [extensions](docs/rules/extensions.md) | Enforce that import statements either always include or never include allowed file extensions. | | | | 🔧 | | |
77-
7872
### Style guide
7973

8074
| Name                            | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 ||
8175
| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | :- | :---- | :- | :- | :- | :- |
8276
| [consistent-type-specifier-style](docs/rules/consistent-type-specifier-style.md) | Enforce or ban the use of inline type-only markers for named imports. | | | | 🔧 | | |
8377
| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md) | Enforce a leading comment with the webpackChunkName for dynamic imports. | | | | | 💡 | |
8478
| [exports-last](docs/rules/exports-last.md) | Ensure all exports appear after other statements. | | | | | | |
79+
| [extensions](docs/rules/extensions.md) | Ensure consistent use of file extension within the import path. | | | | 🔧 | | |
8580
| [first](docs/rules/first.md) | Ensure all imports appear before other statements. | | | | 🔧 | | |
8681
| [group-exports](docs/rules/group-exports.md) | Prefer named exports to be grouped together in a single export declaration | | | | | | |
8782
| [imports-first](docs/rules/imports-first.md) | Replaced by `import/first`. | | | | 🔧 | ||

src/rules/extensions.js

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ function buildProperties(context) {
9191

9292
module.exports = {
9393
meta: {
94-
type: 'problem',
94+
type: 'suggestion',
9595
docs: {
96-
description: 'Enforce that import statements either always include or never include allowed file extensions.',
97-
category: 'Static Analysis',
96+
category: 'Style guide',
97+
description: 'Ensure consistent use of file extension within the import path.',
9898
recommended: false,
9999
url: docsUrl('extensions'),
100100
},
@@ -134,12 +134,6 @@ module.exports = {
134134
},
135135
],
136136
},
137-
messages: {
138-
missingExtension:
139-
'Missing file extension for "{{importPath}}" (expected {{expected}}).',
140-
unexpectedExtension:
141-
'Unexpected file extension "{{extension}}" in import of "{{importPath}}".',
142-
},
143137
},
144138

145139
create(context) {
@@ -158,12 +152,7 @@ module.exports = {
158152
return getModifier(extension) === 'never';
159153
}
160154

161-
// Updated: This helper now determines resolvability based on the passed options.
162-
// If the configured option for the extension is "never", we return true immediately.
163-
function isResolvableWithoutExtension(file, ext) {
164-
if (isUseOfExtensionForbidden(ext)) {
165-
return true;
166-
}
155+
function isResolvableWithoutExtension(file) {
167156
const fileExt = path.extname(file);
168157
const fileWithoutExtension = file.slice(0, -fileExt.length);
169158
const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context);
@@ -201,7 +190,9 @@ module.exports = {
201190
if (!source || !source.value) { return; }
202191

203192
const importPathWithQueryString = source.value;
193+
const hasQuery = importPathWithQueryString.includes('?');
204194
const currentDir = path.dirname(context.getFilename());
195+
const isRelative = importPathWithQueryString.startsWith('.');
205196

206197
// If not undefined, the user decided if rules are enforced on this import
207198
const overrideAction = computeOverrideAction(
@@ -213,58 +204,82 @@ module.exports = {
213204
return;
214205
}
215206

216-
// don't enforce anything on builtins
217207
if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) { return; }
218208

219-
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '');
220-
221-
// don't enforce in root external packages as they may have names with `.js`.
222-
// Like `import Decimal from decimal.js`)
223-
if (!overrideAction && isExternalRootModule(importPath)) { return; }
209+
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '').trim();
210+
if (!overrideAction && isExternalRootModule(importPath) && !(props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type'))) { return; }
224211

225212
const resolvedPath = resolve(importPath, context);
226-
const extensionWithDot = path.extname(resolvedPath || importPath);
213+
const isPackage = isExternalModule(importPath, resolvedPath, context) || isScoped(importPath);
214+
const extension = path.extname(resolvedPath || importPath).slice(1);
227215

228-
// determine if this is a module
229-
const isPackage = isExternalModule(
230-
importPath,
231-
resolve(importPath, context),
232-
context,
233-
) || isScoped(importPath);
216+
const sourceCode = context.getSourceCode();
217+
const fileHasExports = sourceCode.ast.body.some((n) => n.type.indexOf('Export') === 0);
218+
const isExport = node && node.type && node.type.indexOf('Export') === 0;
219+
const isImportDeclaration = node && node.type === 'ImportDeclaration';
234220

235-
// Case 1: Missing extension.
236-
if (!extensionWithDot || !importPath.endsWith(extensionWithDot)) {
221+
if (!extension || !importPath.endsWith(`.${extension}`)) {
237222
// ignore type-only imports and exports
238223
if (!props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type')) { return; }
239-
const candidate = getCandidateExtension(importPath, currentDir);
240-
if (candidate && isUseOfExtensionRequired(candidate.replace(/^\./, ''), isPackage)) {
241-
context.report({
242-
node,
243-
messageId: 'missingExtension',
244-
data: {
245-
importPath: importPathWithQueryString,
246-
expected: candidate,
247-
},
248-
fix(fixer) {
249-
return fixer.replaceText(source, JSON.stringify(importPathWithQueryString + candidate));
250-
},
251-
});
224+
let candidate = extension ? `.${extension}` : getCandidateExtension(importPath, currentDir);
225+
if (!candidate && isUseOfExtensionRequired('js', isPackage)) { candidate = '.js'; }
226+
if (candidate && isUseOfExtensionRequired(candidate.slice(1), isPackage)) {
227+
if (isExport || hasQuery || !isImportDeclaration && fileHasExports || !Object.prototype.hasOwnProperty.call(props.pattern, candidate.slice(1)) || !isRelative || isPackage) {
228+
context.report({
229+
node: source,
230+
message: `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPathWithQueryString}"`,
231+
data: {
232+
importPath: importPathWithQueryString,
233+
expected: candidate,
234+
},
235+
});
236+
} else {
237+
context.report({
238+
node: source,
239+
message: `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPathWithQueryString}"`,
240+
data: {
241+
importPath: importPathWithQueryString,
242+
expected: candidate,
243+
},
244+
fix(fixer) {
245+
return fixer.replaceText(
246+
source,
247+
JSON.stringify(importPathWithQueryString + candidate),
248+
);
249+
},
250+
});
251+
}
252252
}
253253
} else {
254254
// Case 2: Unexpected extension provided.
255-
const extension = extensionWithDot.substring(1);
256-
if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath, extension)) {
257-
context.report({
258-
node: source,
259-
messageId: 'unexpectedExtension',
260-
data: {
261-
extension,
262-
importPath: importPathWithQueryString,
263-
},
264-
fix(fixer) {
265-
return fixer.replaceText(source, JSON.stringify(importPath.slice(0, -extensionWithDot.length)));
266-
},
267-
});
255+
if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
256+
if (isExport || hasQuery || !isImportDeclaration && fileHasExports || !Object.prototype.hasOwnProperty.call(props.pattern, extension) || !isRelative || isPackage) {
257+
context.report({
258+
node: source,
259+
message: `Unexpected use of file extension "${extension}" for "${importPathWithQueryString}"`,
260+
data: {
261+
extension,
262+
importPath: importPathWithQueryString,
263+
},
264+
});
265+
} else {
266+
context.report({
267+
node: source,
268+
message: `Unexpected use of file extension "${extension}" for "${importPathWithQueryString}"`,
269+
data: {
270+
extension,
271+
importPath: importPathWithQueryString,
272+
},
273+
fix(fixer) {
274+
return fixer.replaceText(
275+
source,
276+
JSON.stringify(
277+
importPath.slice(0, -(extension.length + 1)),
278+
),
279+
);
280+
},
281+
});
282+
}
268283
}
269284
}
270285
}

0 commit comments

Comments
 (0)