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(typescript-estree): add EXPERIMENTAL_useProjectService option to use TypeScript project service #6754
feat(typescript-estree): add EXPERIMENTAL_useProjectService option to use TypeScript project service #6754
Changes from 12 commits
550465b
6ea9f07
05f4d99
1fed323
ecd5d3d
78f8c4a
76acaeb
a68dece
6f6f02a
e43145f
214fc8d
a2f71a7
4ffaffa
20d9f82
6ef8498
195462d
0db7485
b583bb3
5b9ca22
a9aec01
4ab1b3d
be6cc5d
80110c1
c326886
a5772e2
128837a
4f52573
b1da422
814a8ad
a5180c7
37d3548
00ddecd
b5d76d4
da45669
69be30f
93d369c
7e74202
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 |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import * as tsserver from 'typescript/lib/tsserverlibrary'; | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const doNothing = (): void => {}; | ||
|
||
const createStubFileWatcher = (): tsserver.FileWatcher => ({ | ||
close: doNothing, | ||
}); | ||
|
||
export function createProjectService(): tsserver.server.ProjectService { | ||
// TODO: see getWatchProgramsForProjects | ||
// We don't watch the disk, we just refer to these when ESLint calls us | ||
// there's a whole separate update pass in maybeInvalidateProgram at the bottom of getWatchProgramsForProjects | ||
// (this "goes nuclear on TypeScript") | ||
const system: tsserver.server.ServerHost = { | ||
...tsserver.sys, | ||
clearImmediate, | ||
clearTimeout, | ||
setImmediate, | ||
setTimeout, | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
watchDirectory: createStubFileWatcher, | ||
watchFile: createStubFileWatcher, | ||
}; | ||
|
||
const projectService = new tsserver.server.ProjectService({ | ||
host: system, | ||
cancellationToken: { isCancellationRequested: (): boolean => false }, | ||
useSingleInferredProject: false, | ||
useInferredProjectPerProjectRoot: false, | ||
// TODO: https://github.com/microsoft/TypeScript/issues/53803 | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
typingsInstaller: { | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
attach: (): void => {}, | ||
enqueueInstallTypingsRequest: (): void => {}, | ||
installPackage: (): Promise<never> => { | ||
throw new Error('This should never be called.'); | ||
}, | ||
isKnownTypesPackageName: () => false, | ||
onProjectClosed: (): void => {}, | ||
globalTypingsCacheLocation: '', | ||
}, | ||
logger: { | ||
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. probably be good to wire this up to our 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. Ooh good point. I'll leave that as a followup issue 👍 it'd be a good fodder for getting someone other than me involved in this area of code. |
||
close: doNothing, | ||
endGroup: doNothing, | ||
getLogFileName: () => undefined, | ||
hasLevel: () => false, | ||
info: doNothing, | ||
loggingEnabled: () => false, | ||
msg: doNothing, | ||
perftrc: doNothing, | ||
startGroup: doNothing, | ||
}, | ||
session: undefined, | ||
}); | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return projectService; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import type * as ts from 'typescript'; | ||
import type * as tsserverlibrary from 'typescript/lib/tsserverlibrary'; | ||
|
||
import type { CanonicalPath } from '../create-program/shared'; | ||
import type { TSESTree } from '../ts-estree'; | ||
|
@@ -57,6 +58,13 @@ export interface MutableParseSettings { | |
*/ | ||
errorOnUnknownASTType: boolean; | ||
|
||
/** | ||
* Experimental: TypeScript server to power program creation. | ||
*/ | ||
EXPERIMENTAL_projectService: | ||
| tsserverlibrary.server.ProjectService | ||
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. Will including this have a negative effect for those who are importing this, since now two big TS d.ts files will get loaded? Maybe not too many use this, but, I'm just thinking ahead. 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. Hmm, I hope not! So far it hasn't hurt anything I've noticed. 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. This will actually leak out into the public types accidentally. To properly isolate this Josh we'll probably want to refactor I was poking around via 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 see what you mean - as a quicker / less change-y step, I just changed the parameters for these functions to be their own types (or just a 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. that fixes the issue, yeah because we no longer leak 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 was actually originally referring to the type checking, in that users of your API will type check TS's huge API twice. 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. Is there any alternative that would prevent this from being visible externally, or is this definitely a requirement? 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. 🤔 long-term I think we really want this to be visible externally. I want to investigate integrating with other project service consumers such as https://github.com/Quramy/typescript-eslint-language-service (cc @Quramy). I don't think I follow what you mean by users of our API type checking the huge API twice - do you mean because if this file's If so: I suppose in theory we could hand-write a 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. If someone imports Probably I should revisit the "merge typescript and tsserverlibrary" idea, I had just been working on other stuff. 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. Ah, got it thanks. I think this is fine for now. I'm under the impression that very few consumers import from any |
||
| undefined; | ||
|
||
/** | ||
* Whether TS should use the source files for referenced projects instead of the compiled .d.ts files. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import type * as ts from 'typescript'; | ||
import type { server } from 'typescript/lib/tsserverlibrary'; | ||
|
||
import { createProjectProgram } from './create-program/createProjectProgram'; | ||
import type { ASTAndDefiniteProgram } from './create-program/shared'; | ||
import type { MutableParseSettings } from './parseSettings'; | ||
|
||
export function useProgramFromProjectService( | ||
projectService: server.ProjectService, | ||
parseSettings: Readonly<MutableParseSettings>, | ||
): ASTAndDefiniteProgram | undefined { | ||
projectService.setCompilerOptionsForInferredProjects({ | ||
rootDir: parseSettings.tsconfigRootDir, | ||
}); | ||
|
||
projectService.openClientFile( | ||
parseSettings.filePath, | ||
parseSettings.codeFullText, | ||
/* scriptKind */ undefined, | ||
parseSettings.tsconfigRootDir, | ||
); | ||
JoshuaKGoldberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const scriptInfo = projectService.getScriptInfo(parseSettings.filePath); | ||
const program = projectService | ||
.getDefaultProjectForFile(scriptInfo!.fileName, true)! | ||
.getLanguageService(/*ensureSynchronized*/ true) | ||
bradzacher marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.getProgram(); | ||
|
||
if (!program) { | ||
return undefined; | ||
} | ||
|
||
return createProjectProgram(parseSettings, [program as ts.Program]); | ||
} |
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.
This was a great thing to add!