Skip to content
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

RFC: Language service extensions and tests #9283

Merged
25 changes: 22 additions & 3 deletions src/compiler/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,30 @@ namespace ts {
new (state: {ts: typeof ts, args: any, host: CompilerHost, program: Program, checker: TypeChecker}): LintWalker;
}

export interface LanguageServiceHost {} // The members for these interfaces are provided in the services layer
export interface LanguageService {}
export interface LanguageServiceProvider {}
export interface DocumentRegistry {}

export interface LanguageServiceProviderStatic extends BaseProviderStatic {
readonly ["extension-kind"]: ExtensionKind.LanguageService;
new (state: { ts: typeof ts, args: any, host: LanguageServiceHost, service: LanguageService, registry: DocumentRegistry }): LanguageServiceProvider;
}

export namespace ExtensionKind {
export const SemanticLint: "semantic-lint" = "semantic-lint";
export type SemanticLint = "semantic-lint";
export const SyntacticLint: "syntactic-lint" = "syntactic-lint";
export type SyntacticLint = "syntactic-lint";
export const LanguageService: "language-service" = "language-service";
export type LanguageService = "language-service";
}
export type ExtensionKind = ExtensionKind.SemanticLint | ExtensionKind.SyntacticLint;
export type ExtensionKind = ExtensionKind.SemanticLint | ExtensionKind.SyntacticLint | ExtensionKind.LanguageService;

export interface ExtensionCollectionMap {
"syntactic-lint"?: SyntacticLintExtension[];
"semantic-lint"?: SemanticLintExtension[];
"language-service"?: LanguageServiceExtension[];
[index: string]: Extension[] | undefined;
}

Expand All @@ -62,7 +75,12 @@ namespace ts {
ctor: SemanticLintProviderStatic;
}

export type Extension = SyntacticLintExtension | SemanticLintExtension;
// @kind(ExtensionKind.LanguageService)
export interface LanguageServiceExtension extends ExtensionBase {
ctor: LanguageServiceProviderStatic;
}

export type Extension = SyntacticLintExtension | SemanticLintExtension | LanguageServiceExtension;

export interface ExtensionCache {
getCompilerExtensions(): ExtensionCollectionMap;
Expand Down Expand Up @@ -141,6 +159,7 @@ namespace ts {
switch (ext.kind) {
case ExtensionKind.SemanticLint:
case ExtensionKind.SyntacticLint:
case ExtensionKind.LanguageService:
if (typeof potentialExtension !== "function") {
diagnostics.push(createCompilerDiagnostic(
Diagnostics.Extension_0_exported_member_1_has_extension_kind_2_but_was_type_3_when_type_4_was_expected,
Expand All @@ -152,7 +171,7 @@ namespace ts {
));
return;
}
(ext as (SemanticLintExtension | SyntacticLintExtension)).ctor = potentialExtension as (SemanticLintProviderStatic | SyntacticLintProviderStatic);
(ext as (SemanticLintExtension | SyntacticLintExtension | LanguageServiceExtension)).ctor = potentialExtension as (SemanticLintProviderStatic | SyntacticLintProviderStatic | LanguageServiceProviderStatic);
break;
default:
// Include a default case which just puts the extension unchecked onto the base extension
Expand Down
150 changes: 150 additions & 0 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,86 @@ namespace ts {
dispose(): void;
}

export interface LanguageServiceProvider {
// Overrides

// A plugin can implement one of the override methods to replace the results that would
// be returned by the TypeScript language service. If a plugin returns a defined results
// (that is, is not undefined) then that result is used instead of invoking the
// corresponding TypeScript method. If multiple plugins are registered, they are
// consulted in the order they are returned from the program. The first defined result
// returned by a plugin is used and no other plugin overrides are consulted.

getProgramDiagnostics?(): Diagnostic[];
getSyntacticDiagnostics?(fileName: string): Diagnostic[];
getSemanticDiagnostics?(fileName: string): Diagnostic[];
getEncodedSyntacticClassifications?(fileName: string, span: TextSpan): Classifications;
getEncodedSemanticClassifications?(fileName: string, span: TextSpan): Classifications;
getCompletionsAtPosition?(fileName: string, position: number): CompletionInfo;
getCompletionEntryDetails?(fileName: string, position: number, entryName: string): CompletionEntryDetails;
getQuickInfoAtPosition?(fileName: string, position: number): QuickInfo;
getNameOrDottedNameSpan?(fileName: string, startPos: number, endPos: number): TextSpan;
getBreakpointStatementAtPosition?(fileName: string, position: number): TextSpan;
getSignatureHelpItems?(fileName: string, position: number): SignatureHelpItems;
getRenameInfo?(fileName: string, position: number): RenameInfo;
findRenameLocations?(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[];
getDefinitionAtPosition?(fileName: string, position: number): DefinitionInfo[];
getTypeDefinitionAtPosition?(fileName: string, position: number): DefinitionInfo[];
getReferencesAtPosition?(fileName: string, position: number): ReferenceEntry[];
findReferences?(fileName: string, position: number): ReferencedSymbol[];
getDocumentHighlights?(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[];
getNavigateToItems?(searchValue: string, maxResultCount: number): NavigateToItem[];
getNavigationBarItems?(fileName: string): NavigationBarItem[];
getOutliningSpans?(fileName: string): OutliningSpan[];
getTodoComments?(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[];
getBraceMatchingAtPosition?(fileName: string, position: number): TextSpan[];
getIndentationAtPosition?(fileName: string, position: number, options: EditorOptions): number;
getFormattingEditsForRange?(fileName: string, start: number, end: number, options: FormatCodeOptions): TextChange[];
getFormattingEditsForDocument?(fileName: string, options: FormatCodeOptions): TextChange[];
getFormattingEditsAfterKeystroke?(fileName: string, position: number, key: string, options: FormatCodeOptions): TextChange[];
getDocCommentTemplateAtPosition?(fileName: string, position: number): TextInsertion;

// Filters

// A plugin can implement one of the filter methods to augment, extend or modify a result
// prior to the host receiving it. The TypeScript language service is invoked and the
// result is passed to the plugin as the value of the previous parameter. If more than one
// plugin is registered, the plugins are consulted in the order they are returned from the
// program. The value passed in as previous is the result returned by the prior plugin. If a
// plugin returns undefined, the result passed in as previous is used and the undefined
// result is ignored. All plugins are consulted before the result is returned to the host.
// If a plugin overrides behavior of the method, no filter methods are consulted.

getProgramDiagnosticsFilter?(previous: Diagnostic[]): Diagnostic[];
getSyntacticDiagnosticsFilter?(fileName: string, previous: Diagnostic[]): Diagnostic[];
getSemanticDiagnosticsFilter?(fileName: string, previous: Diagnostic[]): Diagnostic[];
getEncodedSyntacticClassificationsFilter?(fileName: string, span: TextSpan, previous: Classifications): Classifications;
getEncodedSemanticClassificationsFilter?(fileName: string, span: TextSpan, previous: Classifications): Classifications;
getCompletionsAtPositionFilter?(fileName: string, position: number, previous: CompletionInfo): CompletionInfo;
getCompletionEntryDetailsFilter?(fileName: string, position: number, entryName: string, previous: CompletionEntryDetails): CompletionEntryDetails;
getQuickInfoAtPositionFilter?(fileName: string, position: number, previous: QuickInfo): QuickInfo;
getNameOrDottedNameSpanFilter?(fileName: string, startPos: number, endPos: number, previous: TextSpan): TextSpan;
getBreakpointStatementAtPositionFilter?(fileName: string, position: number, previous: TextSpan): TextSpan;
getSignatureHelpItemsFilter?(fileName: string, position: number, previous: SignatureHelpItems): SignatureHelpItems;
getRenameInfoFilter?(fileName: string, position: number, previous: RenameInfo): RenameInfo;
findRenameLocationsFilter?(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, previous: RenameLocation[]): RenameLocation[];
getDefinitionAtPositionFilter?(fileName: string, position: number, previous: DefinitionInfo[]): DefinitionInfo[];
getTypeDefinitionAtPositionFilter?(fileName: string, position: number, previous: DefinitionInfo[]): DefinitionInfo[];
getReferencesAtPositionFilter?(fileName: string, position: number, previous: ReferenceEntry[]): ReferenceEntry[];
findReferencesFilter?(fileName: string, position: number, previous: ReferencedSymbol[]): ReferencedSymbol[];
getDocumentHighlightsFilter?(fileName: string, position: number, filesToSearch: string[], previous: DocumentHighlights[]): DocumentHighlights[];
getNavigateToItemsFilter?(searchValue: string, maxResultCount: number, previous: NavigateToItem[]): NavigateToItem[];
getNavigationBarItemsFilter?(fileName: string, previous: NavigationBarItem[]): NavigationBarItem[];
getOutliningSpansFilter?(fileName: string, previous: OutliningSpan[]): OutliningSpan[];
getTodoCommentsFilter?(fileName: string, descriptors: TodoCommentDescriptor[], previous: TodoComment[]): TodoComment[];
getBraceMatchingAtPositionFilter?(fileName: string, position: number, previous: TextSpan[]): TextSpan[];
getIndentationAtPositionFilter?(fileName: string, position: number, options: EditorOptions, previous: number): number;
getFormattingEditsForRangeFilter?(fileName: string, start: number, end: number, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
getFormattingEditsForDocumentFilter?(fileName: string, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
getFormattingEditsAfterKeystrokeFilter?(fileName: string, position: number, key: string, options: FormatCodeOptions, previous: TextChange[]): TextChange[];
getDocCommentTemplateAtPositionFilter?(fileName: string, position: number, previous: TextInsertion): TextInsertion;
}

export interface Classifications {
spans: number[];
endOfLineState: EndOfLineState;
Expand Down Expand Up @@ -2920,6 +3000,76 @@ namespace ts {
}

export function createLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
const baseService = createUnextendedLanguageService(host, documentRegistry);
const extensions = baseService.getProgram().getCompilerExtensions()["language-service"];
const instantiatedExtensions = map(extensions, extension => new extension.ctor({ ts, host, service: baseService, registry: documentRegistry, args: extension.args }));
const extensionCount = instantiatedExtensions && instantiatedExtensions.length;

function wrap(key: string): Function {
if (extensionCount) {
return (...args: any[]) => {
for (let i = 0; i < extensionCount; i++) {
const extension = instantiatedExtensions[i];
if ((extension as any)[key]) {
const temp = (extension as any)[key](...args);
if (temp !== undefined) {
return temp;
}
}
}
let result: any = (baseService as any)[key](...args);
const filterKey = `${key}Filter`;
for (let i = 0; i < extensionCount; i++) {
const extension = instantiatedExtensions[i];
if ((extension as any)[filterKey]) {
const temp = (extension as any)[filterKey](...args, result);
if (temp !== undefined) {
result = temp;
}
}
}
return result;
};
}
return (baseService as any)[key];
}

function buildWrappedService(underlyingMembers: Map<any>, wrappedMembers: string[]): LanguageService {
// Add wrapped members to map
forEach(wrappedMembers, member => {
underlyingMembers[member] = wrap(member);
});
// Map getProgramDiagnostics to deprecated getCompilerOptionsDiagnostics
underlyingMembers["getCompilerOptionsDiagnostics"] = underlyingMembers["getProgramDiagnostics"];
return underlyingMembers as LanguageService;
}

return buildWrappedService({
cleanupSemanticCache: () => baseService.cleanupSemanticCache(),
getSyntacticClassifications: (fileName: string, span: TextSpan) => baseService.getSyntacticClassifications(fileName, span),
getSemanticClassifications: (fileName: string, span: TextSpan) => baseService.getSemanticClassifications(fileName, span),
getOccurrencesAtPosition: (fileName: string, position: number) => baseService.getOccurrencesAtPosition(fileName, position),
isValidBraceCompletionAtPosition: (fileName: string, pos: number, openingBrace: number) => baseService.isValidBraceCompletionAtPosition(fileName, pos, openingBrace),
getEmitOutput: (fileName: string) => baseService.getEmitOutput(fileName),
getProgram: () => baseService.getProgram(),
getNonBoundSourceFile: (fileName: string) => baseService.getNonBoundSourceFile(fileName),
dispose: () => baseService.dispose(),
}, [
"getSyntacticDiagnostics", "getSemanticDiagnostics", "getProgramDiagnostics",
"getEncodedSyntacticClassifications", "getEncodedSemanticClassifications", "getCompletionsAtPosition",
"getCompletionEntryDetails", "getQuickInfoAtPosition", "getNameOrDottedNameSpan",
"getBreakpointStatementAtPosition", "getSignatureHelpItems", "getRenameInfo",
"findRenameLocations", "getDefinitionAtPosition", "getTypeDefinitionAtPosition",
"getReferencesAtPosition", "findReferences", "getDocumentHighlights",
"getNavigateToItems", "getNavigationBarItems", "getOutliningSpans",
"getTodoComments", "getBraceMatchingAtPosition", "getIndentationAtPosition",
"getFormattingEditsForRange", "getFormattingEditsForDocument", "getFormattingEditsAfterKeystroke",
"getDocCommentTemplateAtPosition"
]);
}

export function createUnextendedLanguageService(host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {

const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
Expand Down
Loading