@@ -134,10 +134,6 @@ module.exports = {
134
134
} ,
135
135
] ,
136
136
} ,
137
- messages : {
138
- missingExtension : 'Missing file extension for "{{importPath}}" (expected {{expected}}).' ,
139
- unexpectedExtension : 'Unexpected use of file extension "{{extension}}" for "{{importPath}}"' ,
140
- } ,
141
137
} ,
142
138
143
139
create ( context ) {
@@ -156,7 +152,6 @@ module.exports = {
156
152
return getModifier ( extension ) === 'never' ;
157
153
}
158
154
159
- // If the configured option for the extension is "never", we return true immediately.
160
155
function isResolvableWithoutExtension ( file , ext ) {
161
156
if ( isUseOfExtensionForbidden ( ext ) ) {
162
157
return true ;
@@ -198,7 +193,9 @@ module.exports = {
198
193
if ( ! source || ! source . value ) { return ; }
199
194
200
195
const importPathWithQueryString = source . value ;
196
+ const hasQuery = importPathWithQueryString . includes ( '?' ) ;
201
197
const currentDir = path . dirname ( context . getFilename ( ) ) ;
198
+ const isRelative = importPathWithQueryString . startsWith ( '.' ) ;
202
199
203
200
// If not undefined, the user decided if rules are enforced on this import
204
201
const overrideAction = computeOverrideAction (
@@ -210,59 +207,85 @@ module.exports = {
210
207
return ;
211
208
}
212
209
213
- // don't enforce anything on builtins
214
210
if ( ! overrideAction && isBuiltIn ( importPathWithQueryString , context . settings ) ) { return ; }
215
211
216
212
const importPath = importPathWithQueryString . replace ( / \? ( .* ) $ / , '' ) ;
217
-
218
- // don't enforce in root external packages as they may have names with `.js`.
219
- // Like `import Decimal from decimal.js`)
220
- if ( ! overrideAction && isExternalRootModule ( importPath ) ) { return ; }
213
+ if ( ! overrideAction && isExternalRootModule ( importPath ) && ! ( props . checkTypeImports && ( node . importKind === 'type' || node . exportKind === 'type' ) ) ) { return ; }
221
214
222
215
const resolvedPath = resolve ( importPath , context ) ;
223
- const extensionWithDot = path . extname ( resolvedPath || importPath ) ;
216
+ const isPackage = isExternalModule ( importPath , resolvedPath , context ) || isScoped ( importPath ) ;
217
+ const extension = path . extname ( resolvedPath || importPath ) . slice ( 1 ) ;
224
218
225
- // determine if this is a module
226
- const isPackage = isExternalModule (
227
- importPath ,
228
- resolve ( importPath , context ) ,
229
- context ,
230
- ) || isScoped ( importPath ) ;
219
+ const sourceCode = context . getSourceCode ( ) ;
220
+ const fileHasExports = sourceCode . ast . body . some ( ( n ) => n . type . indexOf ( 'Export' ) === 0 ) ;
221
+ const isExport = node && node . type && node . type . indexOf ( 'Export' ) === 0 ;
222
+ const isImportDeclaration = node && node . type === 'ImportDeclaration' ;
231
223
232
- // Case 1: Missing extension.
233
- if ( ! extensionWithDot || ! importPath . endsWith ( extensionWithDot ) ) {
224
+ if ( ! extension || ! importPath . endsWith ( `.${ extension } ` ) ) {
234
225
// ignore type-only imports and exports
235
226
if ( ! props . checkTypeImports && ( node . importKind === 'type' || node . exportKind === 'type' ) ) { return ; }
236
- const candidate = getCandidateExtension ( importPath , currentDir ) ;
237
- if ( candidate && isUseOfExtensionRequired ( candidate . replace ( / ^ \. / , '' ) , isPackage ) ) {
238
- context . report ( {
239
- node,
240
- message :
241
- `Missing file extension for "${ importPathWithQueryString } "` ,
242
- data : {
243
- importPath : importPathWithQueryString ,
244
- expected : candidate ,
245
- } ,
246
- fix ( fixer ) {
247
- return fixer . replaceText ( source , JSON . stringify ( importPathWithQueryString + candidate ) ) ;
248
- } ,
249
- } ) ;
227
+ let candidate = getCandidateExtension ( importPath , currentDir ) ;
228
+ if ( ! candidate && isUseOfExtensionRequired ( 'js' , isPackage ) ) { candidate = '.js' ; }
229
+ if ( candidate && isUseOfExtensionRequired ( candidate . slice ( 1 ) , isPackage ) ) {
230
+ if ( isExport || hasQuery || ! isImportDeclaration && fileHasExports || ! Object . prototype . hasOwnProperty . call (
231
+ props . pattern ,
232
+ candidate . slice ( 1 ) ,
233
+ ) || ! isRelative || isPackage ) {
234
+ context . report ( {
235
+ node : source ,
236
+ message : `Missing file extension ${ extension ? `"${ extension } " ` : '' } for "${ importPathWithQueryString } "` ,
237
+ data : {
238
+ importPath : importPathWithQueryString ,
239
+ expected : candidate ,
240
+ } ,
241
+ } ) ;
242
+ } else {
243
+ context . report ( {
244
+ node : source ,
245
+ message : `Missing file extension ${ extension ? `"${ extension } " ` : '' } for "${ importPathWithQueryString } "` ,
246
+ data : {
247
+ importPath : importPathWithQueryString ,
248
+ expected : candidate ,
249
+ } ,
250
+ fix ( fixer ) {
251
+ return fixer . replaceText (
252
+ source ,
253
+ JSON . stringify ( importPathWithQueryString + candidate ) ,
254
+ ) ;
255
+ } ,
256
+ } ) ;
257
+ }
250
258
}
251
259
} else {
252
260
// Case 2: Unexpected extension provided.
253
- const extension = extensionWithDot . slice ( 1 ) ;
254
261
if ( isUseOfExtensionForbidden ( extension ) && isResolvableWithoutExtension ( importPath , extension ) ) {
255
- context . report ( {
256
- node : source ,
257
- message : `Unexpected use of file extension "${ extension } " for "${ importPathWithQueryString } "` ,
258
- data : {
259
- extension,
260
- importPath : importPathWithQueryString ,
261
- } ,
262
- fix ( fixer ) {
263
- return fixer . replaceText ( source , JSON . stringify ( importPath . slice ( 0 , - extensionWithDot . length ) ) ) ;
264
- } ,
265
- } ) ;
262
+ if ( isExport || hasQuery || ! isImportDeclaration && fileHasExports || ! Object . prototype . hasOwnProperty . call ( props . pattern , extension ) || ! isRelative || isPackage ) {
263
+ context . report ( {
264
+ node : source ,
265
+ message : `Unexpected use of file extension "${ extension } " for "${ importPathWithQueryString } "` ,
266
+ data : {
267
+ extension,
268
+ importPath : importPathWithQueryString ,
269
+ } ,
270
+ } ) ;
271
+ } else {
272
+ context . report ( {
273
+ node : source ,
274
+ message : `Unexpected use of file extension "${ extension } " for "${ importPathWithQueryString } "` ,
275
+ data : {
276
+ extension,
277
+ importPath : importPathWithQueryString ,
278
+ } ,
279
+ fix ( fixer ) {
280
+ return fixer . replaceText (
281
+ source ,
282
+ JSON . stringify (
283
+ importPath . slice ( 0 , - ( extension . length + 1 ) ) ,
284
+ ) ,
285
+ ) ;
286
+ } ,
287
+ } ) ;
288
+ }
266
289
}
267
290
}
268
291
}
0 commit comments