@@ -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 ) {
@@ -198,7 +194,9 @@ module.exports = {
198194 if ( ! source || ! source . value ) { return ; }
199195
200196 const importPathWithQueryString = source . value ;
197+ const hasQuery = importPathWithQueryString . includes ( '?' ) ;
201198 const currentDir = path . dirname ( context . getFilename ( ) ) ;
199+ const isRelative = importPathWithQueryString . startsWith ( '.' ) ;
202200
203201 // If not undefined, the user decided if rules are enforced on this import
204202 const overrideAction = computeOverrideAction (
@@ -210,59 +208,86 @@ module.exports = {
210208 return ;
211209 }
212210
213- // don't enforce anything on builtins
214211 if ( ! overrideAction && isBuiltIn ( importPathWithQueryString , context . settings ) ) { return ; }
215212
216213 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 ; }
214+ if ( ! overrideAction && isExternalRootModule ( importPath ) && ! ( props . checkTypeImports && ( node . importKind === 'type' || node . exportKind === 'type' ) ) ) { return ; }
221215
222216 const resolvedPath = resolve ( importPath , context ) ;
217+ const isPackage = isExternalModule ( importPath , resolvedPath , context ) || isScoped ( importPath ) ;
223218 const extensionWithDot = path . extname ( resolvedPath || importPath ) ;
219+ const extension = extensionWithDot . slice ( 1 ) ;
224220
225- // determine if this is a module
226- const isPackage = isExternalModule (
227- importPath ,
228- resolve ( importPath , context ) ,
229- context ,
230- ) || isScoped ( importPath ) ;
221+ const sourceCode = context . getSourceCode ( ) ;
222+ const fileHasExports = sourceCode . ast . body . some ( ( n ) => n . type . indexOf ( 'Export' ) === 0 ) ;
223+ const isExport = node && node . type && node . type . indexOf ( 'Export' ) === 0 ;
224+ const isImportDeclaration = node && node . type === 'ImportDeclaration' ;
231225
232- // Case 1: Missing extension.
233226 if ( ! extensionWithDot || ! importPath . endsWith ( extensionWithDot ) ) {
234227 // ignore type-only imports and exports
235228 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- } ) ;
229+ let candidate = getCandidateExtension ( importPath , currentDir ) ;
230+ if ( ! candidate && isUseOfExtensionRequired ( 'js' , isPackage ) ) { candidate = '.js' ; }
231+ if ( candidate && isUseOfExtensionRequired ( candidate . slice ( 1 ) , isPackage ) ) {
232+ if ( isExport || hasQuery || ! isImportDeclaration && fileHasExports || ! Object . prototype . hasOwnProperty . call (
233+ props . pattern ,
234+ candidate . slice ( 1 ) ,
235+ ) || ! isRelative || isPackage ) {
236+ context . report ( {
237+ node : source ,
238+ message : `Missing file extension ${ extension ? `"${ extension } " ` : '' } for "${ importPathWithQueryString } "` ,
239+ data : {
240+ importPath : importPathWithQueryString ,
241+ expected : candidate ,
242+ } ,
243+ } ) ;
244+ } else {
245+ context . report ( {
246+ node : source ,
247+ message : `Missing file extension ${ extension ? `"${ extension } " ` : '' } for "${ importPathWithQueryString } "` ,
248+ data : {
249+ importPath : importPathWithQueryString ,
250+ expected : candidate ,
251+ } ,
252+ fix ( fixer ) {
253+ return fixer . replaceText (
254+ source ,
255+ JSON . stringify ( importPathWithQueryString + candidate ) ,
256+ ) ;
257+ } ,
258+ } ) ;
259+ }
250260 }
251261 } else {
252262 // Case 2: Unexpected extension provided.
253- const extension = extensionWithDot . slice ( 1 ) ;
254263 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- } ) ;
264+ if ( isExport || hasQuery || ! isImportDeclaration && fileHasExports || ! Object . prototype . hasOwnProperty . call ( props . pattern , extension ) || ! isRelative || isPackage ) {
265+ context . report ( {
266+ node : source ,
267+ message : `Unexpected use of file extension "${ extension } " for "${ importPathWithQueryString } "` ,
268+ data : {
269+ extension,
270+ importPath : importPathWithQueryString ,
271+ } ,
272+ } ) ;
273+ } else {
274+ context . report ( {
275+ node : source ,
276+ message : `Unexpected use of file extension "${ extension } " for "${ importPathWithQueryString } "` ,
277+ data : {
278+ extension,
279+ importPath : importPathWithQueryString ,
280+ } ,
281+ fix ( fixer ) {
282+ return fixer . replaceText (
283+ source ,
284+ JSON . stringify (
285+ importPath . slice ( 0 , - extensionWithDot . length ) ,
286+ ) ,
287+ ) ;
288+ } ,
289+ } ) ;
290+ }
266291 }
267292 }
268293 }
0 commit comments