2022-11-10 10:43:16 +00:00
'use strict' ;
2024-03-28 02:25:32 +00:00
2022-11-10 10:43:16 +00:00
exports . _ _esModule = true ;
2020-08-25 23:57:08 +00:00
2024-03-28 02:25:32 +00:00
/** @typedef {`.${string}`} Extension */
/** @typedef {NonNullable<import('eslint').Rule.RuleContext['settings']> & { 'import/extensions'?: Extension[], 'import/parsers'?: { [k: string]: Extension[] }, 'import/cache'?: { lifetime: number | '∞' | 'Infinity' } }} ESLintSettings */
2022-11-10 10:43:16 +00:00
const moduleRequire = require ( './module-require' ) . default ;
const extname = require ( 'path' ) . extname ;
const fs = require ( 'fs' ) ;
2020-08-25 23:57:08 +00:00
2022-11-10 10:43:16 +00:00
const log = require ( 'debug' ) ( 'eslint-plugin-import:parse' ) ;
2024-03-28 02:25:32 +00:00
/** @type {(parserPath: NonNullable<import('eslint').Rule.RuleContext['parserPath']>) => unknown} */
2022-11-10 10:43:16 +00:00
function getBabelEslintVisitorKeys ( parserPath ) {
if ( parserPath . endsWith ( 'index.js' ) ) {
const hypotheticalLocation = parserPath . replace ( 'index.js' , 'visitor-keys.js' ) ;
if ( fs . existsSync ( hypotheticalLocation ) ) {
const keys = moduleRequire ( hypotheticalLocation ) ;
return keys . default || keys ;
}
}
return null ;
}
2024-03-28 02:25:32 +00:00
/** @type {(parserPath: import('eslint').Rule.RuleContext['parserPath'], parserInstance: { VisitorKeys: unknown }, parsedResult?: { visitorKeys?: unknown }) => unknown} */
2022-11-10 10:43:16 +00:00
function keysFromParser ( parserPath , parserInstance , parsedResult ) {
// Exposed by @typescript-eslint/parser and @babel/eslint-parser
if ( parsedResult && parsedResult . visitorKeys ) {
return parsedResult . visitorKeys ;
}
2024-03-28 02:25:32 +00:00
if ( typeof parserPath === 'string' && ( /.*espree.*/ ) . test ( parserPath ) ) {
2022-11-10 10:43:16 +00:00
return parserInstance . VisitorKeys ;
}
2024-03-28 02:25:32 +00:00
if ( typeof parserPath === 'string' && ( /.*babel-eslint.*/ ) . test ( parserPath ) ) {
2022-11-10 10:43:16 +00:00
return getBabelEslintVisitorKeys ( parserPath ) ;
}
return null ;
}
// this exists to smooth over the unintentional breaking change in v2.7.
// TODO, semver-major: avoid mutating `ast` and return a plain object instead.
2024-03-28 02:25:32 +00:00
/** @type {<T extends import('eslint').AST.Program>(ast: T, visitorKeys: unknown) => T} */
2022-11-10 10:43:16 +00:00
function makeParseReturn ( ast , visitorKeys ) {
if ( ast ) {
2024-03-28 02:25:32 +00:00
// @ts-expect-error see TODO
2022-11-10 10:43:16 +00:00
ast . visitorKeys = visitorKeys ;
2024-03-28 02:25:32 +00:00
// @ts-expect-error see TODO
2022-11-10 10:43:16 +00:00
ast . ast = ast ;
}
return ast ;
}
2024-03-28 02:25:32 +00:00
/** @type {(text: string) => string} */
2022-11-10 10:43:16 +00:00
function stripUnicodeBOM ( text ) {
return text . charCodeAt ( 0 ) === 0xFEFF ? text . slice ( 1 ) : text ;
}
2024-03-28 02:25:32 +00:00
/** @type {(text: string) => string} */
2022-11-10 10:43:16 +00:00
function transformHashbang ( text ) {
return text . replace ( /^#!([^\r\n]+)/u , ( _ , captured ) => ` // ${ captured } ` ) ;
}
2020-08-25 23:57:08 +00:00
2024-03-28 02:25:32 +00:00
/** @type {import('./parse').default} */
2020-08-25 23:57:08 +00:00
exports . default = function parse ( path , content , context ) {
2024-03-28 02:25:32 +00:00
if ( context == null ) { throw new Error ( 'need context to parse properly' ) ; }
2020-08-25 23:57:08 +00:00
2024-03-28 02:25:32 +00:00
// ESLint in "flat" mode only sets context.languageOptions.parserOptions
let parserOptions = context . languageOptions && context . languageOptions . parserOptions || context . parserOptions ;
const parserOrPath = getParser ( path , context ) ;
2020-08-25 23:57:08 +00:00
2024-03-28 02:25:32 +00:00
if ( ! parserOrPath ) { throw new Error ( 'parserPath or languageOptions.parser is required!' ) ; }
2020-08-25 23:57:08 +00:00
// hack: espree blows up with frozen options
2022-11-10 10:43:16 +00:00
parserOptions = Object . assign ( { } , parserOptions ) ;
parserOptions . ecmaFeatures = Object . assign ( { } , parserOptions . ecmaFeatures ) ;
2020-08-25 23:57:08 +00:00
// always include comments and tokens (for doc parsing)
2022-11-10 10:43:16 +00:00
parserOptions . comment = true ;
parserOptions . attachComment = true ; // keeping this for backward-compat with older parsers
parserOptions . tokens = true ;
2020-08-25 23:57:08 +00:00
// attach node locations
2022-11-10 10:43:16 +00:00
parserOptions . loc = true ;
parserOptions . range = true ;
2020-08-25 23:57:08 +00:00
// provide the `filePath` like eslint itself does, in `parserOptions`
// https://github.com/eslint/eslint/blob/3ec436ee/lib/linter.js#L637
2022-11-10 10:43:16 +00:00
parserOptions . filePath = path ;
2020-08-25 23:57:08 +00:00
// @typescript-eslint/parser will parse the entire project with typechecking if you provide
// "project" or "projects" in parserOptions. Removing these options means the parser will
// only parse one file in isolate mode, which is much, much faster.
2022-11-10 10:43:16 +00:00
// https://github.com/import-js/eslint-plugin-import/issues/1408#issuecomment-509298962
2024-03-28 02:25:32 +00:00
delete parserOptions . EXPERIMENTAL _useProjectService ;
2022-11-10 10:43:16 +00:00
delete parserOptions . project ;
delete parserOptions . projects ;
2020-08-25 23:57:08 +00:00
// require the parser relative to the main module (i.e., ESLint)
2024-03-28 02:25:32 +00:00
const parser = typeof parserOrPath === 'string' ? moduleRequire ( parserOrPath ) : parserOrPath ;
2022-11-10 10:43:16 +00:00
// replicate bom strip and hashbang transform of ESLint
// https://github.com/eslint/eslint/blob/b93af98b3c417225a027cabc964c38e779adb945/lib/linter/linter.js#L779
content = transformHashbang ( stripUnicodeBOM ( String ( content ) ) ) ;
2020-08-25 23:57:08 +00:00
if ( typeof parser . parseForESLint === 'function' ) {
2022-11-10 10:43:16 +00:00
let ast ;
2020-08-25 23:57:08 +00:00
try {
2022-11-10 10:43:16 +00:00
const parserRaw = parser . parseForESLint ( content , parserOptions ) ;
ast = parserRaw . ast ;
2024-03-28 02:25:32 +00:00
// @ts-expect-error TODO: FIXME
return makeParseReturn ( ast , keysFromParser ( parserOrPath , parser , parserRaw ) ) ;
2020-08-25 23:57:08 +00:00
} catch ( e ) {
2022-11-10 10:43:16 +00:00
console . warn ( ) ;
console . warn ( 'Error while parsing ' + parserOptions . filePath ) ;
2024-03-28 02:25:32 +00:00
// @ts-expect-error e is almost certainly an Error here
2022-11-10 10:43:16 +00:00
console . warn ( 'Line ' + e . lineNumber + ', column ' + e . column + ': ' + e . message ) ;
2020-08-25 23:57:08 +00:00
}
if ( ! ast || typeof ast !== 'object' ) {
console . warn (
2024-03-28 02:25:32 +00:00
// Can only be invalid for custom parser per imports/parser
'`parseForESLint` from parser `' + ( typeof parserOrPath === 'string' ? parserOrPath : '`context.languageOptions.parser`' ) + '` is invalid and will just be ignored'
2022-11-10 10:43:16 +00:00
) ;
2020-08-25 23:57:08 +00:00
} else {
2024-03-28 02:25:32 +00:00
// @ts-expect-error TODO: FIXME
return makeParseReturn ( ast , keysFromParser ( parserOrPath , parser , undefined ) ) ;
2020-08-25 23:57:08 +00:00
}
}
2022-11-10 10:43:16 +00:00
const ast = parser . parse ( content , parserOptions ) ;
2024-03-28 02:25:32 +00:00
// @ts-expect-error TODO: FIXME
return makeParseReturn ( ast , keysFromParser ( parserOrPath , parser , undefined ) ) ;
2022-11-10 10:43:16 +00:00
} ;
2020-08-25 23:57:08 +00:00
2024-03-28 02:25:32 +00:00
/** @type {(path: string, context: import('eslint').Rule.RuleContext) => string | null | (import('eslint').Linter.ParserModule)} */
function getParser ( path , context ) {
const parserPath = getParserPath ( path , context ) ;
if ( parserPath ) {
return parserPath ;
}
if (
! ! context . languageOptions
&& ! ! context . languageOptions . parser
&& typeof context . languageOptions . parser !== 'string'
&& (
// @ts-expect-error TODO: figure out a better type
typeof context . languageOptions . parser . parse === 'function'
// @ts-expect-error TODO: figure out a better type
|| typeof context . languageOptions . parser . parseForESLint === 'function'
)
) {
return context . languageOptions . parser ;
}
return null ;
}
/** @type {(path: string, context: import('eslint').Rule.RuleContext & { settings?: ESLintSettings }) => import('eslint').Rule.RuleContext['parserPath']} */
2020-08-25 23:57:08 +00:00
function getParserPath ( path , context ) {
2022-11-10 10:43:16 +00:00
const parsers = context . settings [ 'import/parsers' ] ;
2020-08-25 23:57:08 +00:00
if ( parsers != null ) {
2024-03-28 02:25:32 +00:00
// eslint-disable-next-line no-extra-parens
const extension = /** @type {Extension} */ ( extname ( path ) ) ;
2022-11-10 10:43:16 +00:00
for ( const parserPath in parsers ) {
2020-08-25 23:57:08 +00:00
if ( parsers [ parserPath ] . indexOf ( extension ) > - 1 ) {
// use this alternate parser
2022-11-10 10:43:16 +00:00
log ( 'using alt parser:' , parserPath ) ;
return parserPath ;
2020-08-25 23:57:08 +00:00
}
}
}
// default to use ESLint parser
2022-11-10 10:43:16 +00:00
return context . parserPath ;
2020-08-25 23:57:08 +00:00
}