New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: allow user to provide TS program instance in parser options #3481
Changes from all commits
7c4f35d
4c6aca9
f07d72e
e4cbd2c
648caaf
defed5d
b1f6213
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,5 +48,8 @@ | |
"_ts3.4/*" | ||
] | ||
} | ||
}, | ||
"devDependencies": { | ||
"typescript": ">=2.6.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,4 +1,5 @@ | ||||||
import { Lib } from './lib'; | ||||||
import { Program } from 'typescript'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets ensure that this isn't ever pulled in as a runtime dep on this package.
Suggested change
|
||||||
|
||||||
type DebugLevel = boolean | ('typescript-eslint' | 'eslint' | 'typescript')[]; | ||||||
|
||||||
|
@@ -41,6 +42,7 @@ interface ParserOptions { | |||||
extraFileExtensions?: string[]; | ||||||
filePath?: string; | ||||||
loc?: boolean; | ||||||
program?: Program; | ||||||
project?: string | string[]; | ||||||
projectFolderIgnoreList?: (string | RegExp)[]; | ||||||
range?: boolean; | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -64,6 +64,9 @@ | |||||
"jest-specific-snapshot": "*", | ||||||
"make-dir": "*", | ||||||
"tmp": "^0.2.1", | ||||||
"typescript": "^4.3.2" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keep this as
Suggested change
|
||||||
}, | ||||||
"peerDependencies": { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have an implicit peer dependency via the The reason we don't explicitly declare the peer dependency is because on older versions of yarn and npm, they do not understand In the past this annoyed many users 😅 that had our packages installed (but not used) transitively. TL;DR - remove the explicit peer dep |
||||||
"typescript": "*" | ||||||
}, | ||||||
"peerDependenciesMeta": { | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,28 @@ | ||||||||||
import debug from 'debug'; | ||||||||||
import { Program } from 'typescript'; | ||||||||||
import { Extra } from '../parser-options'; | ||||||||||
import { ASTAndProgram, getAstFromProgram } from './shared'; | ||||||||||
|
||||||||||
const log = debug('typescript-eslint:typescript-estree:useProvidedProgram'); | ||||||||||
|
||||||||||
function useProvidedProgram( | ||||||||||
programInstance: Program, | ||||||||||
extra: Extra, | ||||||||||
): ASTAndProgram | undefined { | ||||||||||
log('Retrieving ast for %s from provided program instance', extra.filePath); | ||||||||||
|
||||||||||
const astAndProgram = getAstFromProgram(programInstance, extra); | ||||||||||
|
||||||||||
if (!astAndProgram) { | ||||||||||
const errorLines = [ | ||||||||||
'"parserOptions.program" has been provided for @typescript-eslint/parser.', | ||||||||||
`The file was not found in the provided program instance: ${extra.filePath}`, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The filepath should be made relative. typescript-eslint/packages/typescript-estree/src/create-program/createProjectProgram.ts Lines 52 to 55 in fc5f171
|
||||||||||
]; | ||||||||||
|
||||||||||
throw new Error(errorLines.join('\n')); | ||||||||||
} | ||||||||||
|
||||||||||
return astAndProgram; | ||||||||||
} | ||||||||||
|
||||||||||
export { useProvidedProgram }; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -18,6 +18,8 @@ import { | |||||
ensureAbsolutePath, | ||||||
getCanonicalFileName, | ||||||
} from './create-program/shared'; | ||||||
import { Program } from 'typescript'; | ||||||
import { useProvidedProgram } from './create-program/useProvidedProgram'; | ||||||
|
||||||
const log = debug('typescript-eslint:typescript-estree:parser'); | ||||||
|
||||||
|
@@ -61,10 +63,12 @@ function enforceString(code: unknown): string { | |||||
*/ | ||||||
function getProgramAndAST( | ||||||
code: string, | ||||||
programInstance: Program | null, | ||||||
shouldProvideParserServices: boolean, | ||||||
shouldCreateDefaultProgram: boolean, | ||||||
): ASTAndProgram { | ||||||
return ( | ||||||
(programInstance && useProvidedProgram(programInstance, extra)) || | ||||||
(shouldProvideParserServices && | ||||||
createProjectProgram(code, shouldCreateDefaultProgram, extra)) || | ||||||
(shouldProvideParserServices && | ||||||
|
@@ -105,6 +109,7 @@ function resetExtra(): void { | |||||
loc: false, | ||||||
log: console.log, // eslint-disable-line no-console | ||||||
preserveNodeMaps: true, | ||||||
program: null, | ||||||
projects: [], | ||||||
range: false, | ||||||
strict: false, | ||||||
|
@@ -264,22 +269,32 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { | |||||
// NOTE - ensureAbsolutePath relies upon having the correct tsconfigRootDir in extra | ||||||
extra.filePath = ensureAbsolutePath(extra.filePath, extra); | ||||||
|
||||||
const projectFolderIgnoreList = ( | ||||||
options.projectFolderIgnoreList ?? ['**/node_modules/**'] | ||||||
) | ||||||
.reduce<string[]>((acc, folder) => { | ||||||
if (typeof folder === 'string') { | ||||||
acc.push(folder); | ||||||
} | ||||||
return acc; | ||||||
}, []) | ||||||
// prefix with a ! for not match glob | ||||||
.map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); | ||||||
// NOTE - prepareAndTransformProjects relies upon having the correct tsconfigRootDir in extra | ||||||
extra.projects = prepareAndTransformProjects( | ||||||
options.project, | ||||||
projectFolderIgnoreList, | ||||||
); | ||||||
if (typeof options.program === 'object') { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit - we don't want to log if the user explicitly passes
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this |
||||||
extra.program = options.program; | ||||||
log( | ||||||
'parserOptions.program was provided, so parserOptions.project will be ignored.', | ||||||
); | ||||||
} | ||||||
|
||||||
if (extra.program === null) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit - just for safety so we can handle the undefined case as well, in case something somewhere is wacky.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this |
||||||
// providing a program overrides project resolution | ||||||
const projectFolderIgnoreList = ( | ||||||
options.projectFolderIgnoreList ?? ['**/node_modules/**'] | ||||||
) | ||||||
.reduce<string[]>((acc, folder) => { | ||||||
if (typeof folder === 'string') { | ||||||
acc.push(folder); | ||||||
} | ||||||
return acc; | ||||||
}, []) | ||||||
// prefix with a ! for not match glob | ||||||
.map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); | ||||||
// NOTE - prepareAndTransformProjects relies upon having the correct tsconfigRootDir in extra | ||||||
extra.projects = prepareAndTransformProjects( | ||||||
options.project, | ||||||
projectFolderIgnoreList, | ||||||
); | ||||||
} | ||||||
|
||||||
if ( | ||||||
Array.isArray(options.extraFileExtensions) && | ||||||
|
@@ -453,6 +468,7 @@ function parseAndGenerateServices<T extends TSESTreeOptions = TSESTreeOptions>( | |||||
extra.projects && extra.projects.length > 0; | ||||||
const { ast, program } = getProgramAndAST( | ||||||
code, | ||||||
extra.program, | ||||||
shouldProvideParserServices, | ||||||
extra.createDefaultProgram, | ||||||
)!; | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use the root package.json to control the TS version installed in the repo.
You can use the
peerDependenciesMeta
to add an optional peer dep on typescript, which we'll need to do to ensure package managers can understand the new requirement