@@ -134,10 +134,6 @@ module.exports = {
134134 } ,
135135 ] ,
136136 } ,
137- messages : {
138- missingExtension : 'Missing file extension for "{{importPath}}" (expected {{expected}}).' ,
139- unexpectedExtension : 'Unexpected use of file extension "{{extension}}" for "{{importPath}}"' ,
140- } ,
141137 } ,
142138
143139 create ( context ) {
@@ -156,7 +152,6 @@ module.exports = {
156152 return getModifier ( extension ) === 'never' ;
157153 }
158154
159- // If the configured option for the extension is "never", we return true immediately.
160155 function isResolvableWithoutExtension ( file , ext ) {
161156 if ( isUseOfExtensionForbidden ( ext ) ) {
162157 return true ;
@@ -198,7 +193,9 @@ module.exports = {
198193 if ( ! source || ! source . value ) { return ; }
199194
200195 const importPathWithQueryString = source . value ;
196+ const hasQuery = importPathWithQueryString . includes ( '?' ) ;
201197 const currentDir = path . dirname ( context . getFilename ( ) ) ;
198+ const isRelative = importPathWithQueryString . startsWith ( '.' ) ;
202199
203200 // If not undefined, the user decided if rules are enforced on this import
204201 const overrideAction = computeOverrideAction (
@@ -210,59 +207,85 @@ module.exports = {
210207 return ;
211208 }
212209
213- // don't enforce anything on builtins
214210 if ( ! overrideAction && isBuiltIn ( importPathWithQueryString , context . settings ) ) { return ; }
215211
216212 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 ; }
221214
222215 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 ) ;
224218
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' ;
231223
232- // Case 1: Missing extension.
233- if ( ! extensionWithDot || ! importPath . endsWith ( extensionWithDot ) ) {
224+ if ( ! extension || ! importPath . endsWith ( `.${ extension } ` ) ) {
234225 // ignore type-only imports and exports
235226 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+ }
250258 }
251259 } else {
252260 // Case 2: Unexpected extension provided.
253- const extension = extensionWithDot . slice ( 1 ) ;
254261 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+ }
266289 }
267290 }
268291 }
0 commit comments