Skip to content

Commit 086e00d

Browse files
Expand auto-import to all package.json dependencies (#38923)
* Start experiment * Add logging * Go back to a single program * Fix forEachExternalModuleToImportFrom * Move auxiliary program to language service * Add logging * Don’t use resolution cache * Fix(?) containingProjects for ScriptInfo in auxiliary program * Fix ScriptInfo project inclusion * Add test for default project of auto-importable ScriptInfo * Add fourslash server test * Don’t create auto import provider inside node_modules * Add monorepo-like test * WIP * Naively ensure autoImportProvider is up to date after package.json change * Start limiting when auto update provider gets updated * Respond to changes in node_modules * Don’t create auto-import provider until a file is open that would use it e.g., don’t create them during cross-project find-all-refs * Clean up naming, @internal marking, and fix empty project creation bug * Drop devDependencies, include peerDependencies * Add additional compiler options * Fix interaction with importSuggestionsCache * Move option to UserPreferences, allow inclusion of devDependencies * Don’t filter out peerDependencies * Watch unparseable package.jsons * But don’t filter packages out due to an invalid package.json * Update test * Don’t use autoImportProvider in codefixes where it can never be used (or any refactors) * Add CompletionEntry property for telemetry * Add assertion for isPackageJsonImport to fourslash * Fix missing pushSymbol argument * Add isPackageJsonImport to tests and API baselines * Fix unit test * Host auto import provider in new Project kind * Fix InferredProject attaching on AutoImportProvider-included files, load eagerly * Update Public APIs * Simplify PackageJsonCache host * Remove unneeded markAsDirty * Defer project finished event until after AutoImportProvider is created * Make AutoImportProviderProject always report isOrphan = true * Close and remove AutoImportProviderProject when host project closes * Don’t set pendingEnsureProjectForOpenFiles * Use hasAddedOrRemovedFiles instead of hasNewProgram * Use host-wide watchOptions for package.json watching * Add to `printProjects` * Clean up * Get autoImportProvider directly from LanguageServiceHost * Clean up * Clean up * Close auto import provider on disableLanguageService * Move AutoImportProvider preload to project updateGraph * Clear auto import suggestion cache when provider program changes * Fix tests * Revert yet-unneeded change * Use projectService host for module resolution host * Don’t re-resolve type directives if nothing has changed * Update src/server/project.ts Co-authored-by: Sheetal Nandi <[email protected]> * Use ts.emptyArray Co-authored-by: Sheetal Nandi <[email protected]>
1 parent d76e85d commit 086e00d

36 files changed

+1101
-222
lines changed

src/compiler/commandLineParser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,7 +2361,7 @@ namespace ts {
23612361
}
23622362

23632363
const result = matchFileNames(filesSpecs, includeSpecs, excludeSpecs, configFileName ? directoryOfCombinedPath(configFileName, basePath) : basePath, options, host, errors, extraFileExtensions, sourceFile);
2364-
if (shouldReportNoInputFiles(result, canJsonReportNoInutFiles(raw), resolutionStack)) {
2364+
if (shouldReportNoInputFiles(result, canJsonReportNoInputFiles(raw), resolutionStack)) {
23652365
errors.push(getErrorForNoInputFiles(result.spec, configFileName));
23662366
}
23672367

@@ -2413,7 +2413,7 @@ namespace ts {
24132413
}
24142414

24152415
/*@internal*/
2416-
export function canJsonReportNoInutFiles(raw: any) {
2416+
export function canJsonReportNoInputFiles(raw: any) {
24172417
return !hasProperty(raw, "files") && !hasProperty(raw, "references");
24182418
}
24192419

src/compiler/tsbuildPublic.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ namespace ts {
11871187
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
11881188
// Update file names
11891189
const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost);
1190-
updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw));
1190+
updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInputFiles(config.raw));
11911191
config.fileNames = result.fileNames;
11921192
watchInputFiles(state, project, projectPath, config);
11931193
}
@@ -1371,7 +1371,7 @@ namespace ts {
13711371
}
13721372

13731373
// Container if no files are specified in the project
1374-
if (!project.fileNames.length && !canJsonReportNoInutFiles(project.raw)) {
1374+
if (!project.fileNames.length && !canJsonReportNoInputFiles(project.raw)) {
13751375
return {
13761376
type: UpToDateStatusType.ContainerOnly
13771377
};

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8024,6 +8024,7 @@ namespace ts {
80248024
readonly importModuleSpecifierEnding?: "auto" | "minimal" | "index" | "js";
80258025
readonly allowTextChangesInNewFiles?: boolean;
80268026
readonly providePrefixAndSuffixTextForRename?: boolean;
8027+
readonly includePackageJsonAutoImports?: "exclude-dev" | "all" | "none";
80278028
readonly provideRefactorNotApplicableReason?: boolean;
80288029
}
80298030

src/compiler/watchPublic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ namespace ts {
671671
configFileSpecs = configFileParseResult.configFileSpecs!; // TODO: GH#18217
672672
projectReferences = configFileParseResult.projectReferences;
673673
configFileParsingDiagnostics = getConfigFileParsingDiagnostics(configFileParseResult).slice();
674-
canConfigFileJsonReportNoInputFiles = canJsonReportNoInutFiles(configFileParseResult.raw);
674+
canConfigFileJsonReportNoInputFiles = canJsonReportNoInputFiles(configFileParseResult.raw);
675675
hasChangedConfigFileParsingErrors = true;
676676
}
677677

src/harness/client.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ namespace ts.server {
131131
this.processResponse(request, /*expectEmptyBody*/ true);
132132
}
133133

134+
/*@internal*/
135+
setFormattingOptions(formatOptions: FormatCodeSettings) {
136+
const args: protocol.ConfigureRequestArguments = { formatOptions };
137+
const request = this.processRequest(CommandNames.Configure, args);
138+
this.processResponse(request, /*expectEmptyBody*/ true);
139+
}
140+
134141
openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
135142
const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName };
136143
this.processRequest(CommandNames.Open, args);
@@ -791,7 +798,11 @@ namespace ts.server {
791798
}
792799

793800
getProgram(): Program {
794-
throw new Error("SourceFile objects are not serializable through the server protocol.");
801+
throw new Error("Program objects are not serializable through the server protocol.");
802+
}
803+
804+
getAutoImportProvider(): Program | undefined {
805+
throw new Error("Program objects are not serializable through the server protocol.");
795806
}
796807

797808
getNonBoundSourceFile(_fileName: string): SourceFile {

src/harness/fourslashImpl.ts

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -871,53 +871,47 @@ namespace FourSlash {
871871
}
872872

873873
private verifyCompletionEntry(actual: ts.CompletionEntry, expected: FourSlashInterface.ExpectedCompletionEntry) {
874-
const { insertText, replacementSpan, hasAction, isRecommended, isFromUncheckedFile, kind, kindModifiers, text, documentation, tags, source, sourceDisplay, sortText } = typeof expected === "string"
875-
? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, isFromUncheckedFile: undefined, kind: undefined, kindModifiers: undefined, text: undefined, documentation: undefined, tags: undefined, source: undefined, sourceDisplay: undefined, sortText: undefined }
876-
: expected;
874+
expected = typeof expected === "string" ? { name: expected } : expected;
877875

878-
if (actual.insertText !== insertText) {
879-
this.raiseError(`Expected completion insert text to be ${insertText}, got ${actual.insertText}`);
876+
if (actual.insertText !== expected.insertText) {
877+
this.raiseError(`Expected completion insert text to be ${expected.insertText}, got ${actual.insertText}`);
880878
}
881-
const convertedReplacementSpan = replacementSpan && ts.createTextSpanFromRange(replacementSpan);
879+
const convertedReplacementSpan = expected.replacementSpan && ts.createTextSpanFromRange(expected.replacementSpan);
882880
try {
883881
assert.deepEqual(actual.replacementSpan, convertedReplacementSpan);
884882
}
885883
catch {
886884
this.raiseError(`Expected completion replacementSpan to be ${stringify(convertedReplacementSpan)}, got ${stringify(actual.replacementSpan)}`);
887885
}
888886

889-
if (kind !== undefined || kindModifiers !== undefined) {
890-
if (actual.kind !== kind) {
891-
this.raiseError(`Unexpected kind for ${actual.name}: Expected '${kind}', actual '${actual.kind}'`);
892-
}
893-
if (actual.kindModifiers !== (kindModifiers || "")) {
894-
this.raiseError(`Bad kindModifiers for ${actual.name}: Expected ${kindModifiers || ""}, actual ${actual.kindModifiers}`);
895-
}
887+
if (expected.kind !== undefined || expected.kindModifiers !== undefined) {
888+
assert.equal(actual.kind, expected.kind, `Expected 'kind' for ${actual.name} to match`);
889+
assert.equal(actual.kindModifiers, expected.kindModifiers || "", `Expected 'kindModifiers' for ${actual.name} to match`);
896890
}
897-
898-
if (isFromUncheckedFile !== undefined) {
899-
if (actual.isFromUncheckedFile !== isFromUncheckedFile) {
900-
this.raiseError(`Expected 'isFromUncheckedFile' value '${actual.isFromUncheckedFile}' to equal '${isFromUncheckedFile}'`);
901-
}
891+
if (expected.isFromUncheckedFile !== undefined) {
892+
assert.equal<boolean | undefined>(actual.isFromUncheckedFile, expected.isFromUncheckedFile, "Expected 'isFromUncheckedFile' properties to match");
893+
}
894+
if (expected.isPackageJsonImport !== undefined) {
895+
assert.equal<boolean | undefined>(actual.isPackageJsonImport, expected.isPackageJsonImport, "Expected 'isPackageJsonImport' properties to match");
902896
}
903897

904-
assert.equal(actual.hasAction, hasAction, `Expected 'hasAction' properties to match`);
905-
assert.equal(actual.isRecommended, isRecommended, `Expected 'isRecommended' properties to match'`);
906-
assert.equal(actual.source, source, `Expected 'source' values to match`);
907-
assert.equal(actual.sortText, sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`));
898+
assert.equal(actual.hasAction, expected.hasAction, `Expected 'hasAction' properties to match`);
899+
assert.equal(actual.isRecommended, expected.isRecommended, `Expected 'isRecommended' properties to match'`);
900+
assert.equal(actual.source, expected.source, `Expected 'source' values to match`);
901+
assert.equal(actual.sortText, expected.sortText || ts.Completions.SortText.LocationPriority, this.messageAtLastKnownMarker(`Actual entry: ${JSON.stringify(actual)}`));
908902

909-
if (text !== undefined) {
903+
if (expected.text !== undefined) {
910904
const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!;
911-
assert.equal(ts.displayPartsToString(actualDetails.displayParts), text, "Expected 'text' property to match 'displayParts' string");
912-
assert.equal(ts.displayPartsToString(actualDetails.documentation), documentation || "", "Expected 'documentation' property to match 'documentation' display parts string");
905+
assert.equal(ts.displayPartsToString(actualDetails.displayParts), expected.text, "Expected 'text' property to match 'displayParts' string");
906+
assert.equal(ts.displayPartsToString(actualDetails.documentation), expected.documentation || "", "Expected 'documentation' property to match 'documentation' display parts string");
913907
// TODO: GH#23587
914908
// assert.equal(actualDetails.kind, actual.kind);
915909
assert.equal(actualDetails.kindModifiers, actual.kindModifiers, "Expected 'kindModifiers' properties to match");
916-
assert.equal(actualDetails.source && ts.displayPartsToString(actualDetails.source), sourceDisplay, "Expected 'sourceDisplay' property to match 'source' display parts string");
917-
assert.deepEqual(actualDetails.tags, tags);
910+
assert.equal(actualDetails.source && ts.displayPartsToString(actualDetails.source), expected.sourceDisplay, "Expected 'sourceDisplay' property to match 'source' display parts string");
911+
assert.deepEqual(actualDetails.tags, expected.tags);
918912
}
919913
else {
920-
assert(documentation === undefined && tags === undefined && sourceDisplay === undefined, "If specifying completion details, should specify 'text'");
914+
assert(expected.documentation === undefined && expected.tags === undefined && expected.sourceDisplay === undefined, "If specifying completion details, should specify 'text'");
921915
}
922916
}
923917

@@ -2122,6 +2116,9 @@ namespace FourSlash {
21222116
public setFormatOptions(formatCodeOptions: ts.FormatCodeOptions | ts.FormatCodeSettings): ts.FormatCodeSettings {
21232117
const oldFormatCodeOptions = this.formatCodeSettings;
21242118
this.formatCodeSettings = ts.toEditorSettings(formatCodeOptions);
2119+
if (this.testType === FourSlashTestType.Server) {
2120+
(this.languageService as ts.server.SessionClient).setFormattingOptions(this.formatCodeSettings);
2121+
}
21252122
return oldFormatCodeOptions;
21262123
}
21272124

src/harness/fourslashInterfaceImpl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ namespace FourSlashInterface {
731731
}
732732

733733
public setOption(name: keyof ts.FormatCodeSettings, value: number | string | boolean): void {
734-
this.state.formatCodeSettings = { ...this.state.formatCodeSettings, [name]: value };
734+
this.state.setFormatOptions({ ...this.state.formatCodeSettings, [name]: value });
735735
}
736736
}
737737

@@ -1495,6 +1495,7 @@ namespace FourSlashInterface {
14951495
readonly isRecommended?: boolean; // If not specified, will assert that this is false.
14961496
readonly isFromUncheckedFile?: boolean; // If not specified, won't assert about this
14971497
readonly kind?: string; // If not specified, won't assert about this
1498+
readonly isPackageJsonImport?: boolean; // If not specified, won't assert about this
14981499
readonly kindModifiers?: string; // Must be paired with 'kind'
14991500
readonly text?: string;
15001501
readonly documentation?: string;

src/harness/harnessLanguageService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,9 @@ namespace Harness.LanguageService {
588588
getProgram(): ts.Program {
589589
throw new Error("Program can not be marshaled across the shim layer.");
590590
}
591+
getAutoImportProvider(): ts.Program | undefined {
592+
throw new Error("Program can not be marshaled across the shim layer.");
593+
}
591594
getNonBoundSourceFile(): ts.SourceFile {
592595
throw new Error("SourceFile can not be marshaled across the shim layer.");
593596
}

0 commit comments

Comments
 (0)