8000 RFC: Extensibility model by weswigham · Pull Request #9038 · microsoft/TypeScript · GitHub
[go: up one dir, main page]

Skip to content

RFC: Extensibility model #9038

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

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8b3aeb8
Functional extension loading & lint passes
weswigham Jun 6, 2016
8583037
Dont look for extensions in a namespace
weswigham Jun 21, 2016
a7ae427
Language service extensions and tests
weswigham Jun 6, 2016
a03e465
Fix comments - make behavior align with comments
weswigham Jun 21, 2016
4eee39d
accept -> stop, add more overloads to error
weswigham Jun 22, 2016
bff6b05
Merge branch 'extensibility-model' into language-service-extensions
weswigham Jun 22, 2016
f4384f6
address @rbuckton and @mhegazy PR comments
weswigham Jun 27, 2016
b874ad9
Use friendlier field name, pass options in property bag
weswigham Jun 27, 2016
d716dc9
Add readonly modifier
weswigham Jun 27, 2016
1433411
Merge branch 'master' into extensibility-model
weswigham Jun 27, 2016
0f9a816
Fix new lints
weswigham Jun 27, 2016
0e9650c
Implement field access per @rbuckton
weswigham Jun 28, 2016
dec0bf8
Fix mock host
weswigham Jun 28, 2016
06dbfb7
Merge branch 'master' into extensibility-model
weswigham Jun 28, 2016
80d89fb
Merge branch 'extensibility-model' into language-service-extensions
weswigham Jun 28, 2016
67f6e26
Add overloads supporting additional shortnames to lint errors
weswigham Jun 28, 2016
2c15bb3
Extract extension cache into new file (so it can be shared/persisted)
weswigham Jun 28, 2016
02834c4
Merge branch 'extensibility-model' into language-service-extensions
weswigham Jun 29, 2016
257e939
Fix lints, identify lifetimeissue
weswigham Jun 29, 2016
9425485
remove excess deep equal overload
weswigham Jun 29, 2016
c2f1cc5
Backport all code related to making extensions run in an LS context
weswigham Jun 29, 2016
3480c7c
Merge branch 'extensibility-model' into language-service-extensions
weswigham Jun 29, 2016
adccddf
Remove whitespace/trailing comma changes
weswigham Jun 29, 2016
af9c1e1
Fix lint
weswigham Jun 29, 2016
908d9e7
Add missing members to type
weswigham Jun 29, 2016
a4762a8
Add extension profiling functions
weswigham Jun 29, 2016
3036843
Rejigger references to make scripts compiler
weswigham Jun 30, 2016
69f0473
Add compiler option to enable extension profiling
weswigham Jun 30, 2016
680a75b
Add profiling for lint extensions
weswigham Jun 30, 2016
19846c3
Encapsulate all the extensions types in the extensions file, ref it f…
weswigham Jul 1, 2016
8283881
Write a test runner for discovering & running extensions tests, remov…
weswigham Jul 2, 2016
4fd46c8
Do away with the Extension diagnostic category
weswigham Jul 2, 2016
7eca070
First set of baselines for the new test runner
weswigham Jul 2, 2016
ab90cba
Fix error baselines, add cache persistence to the LS
weswigham Jul 2, 2016
8a972d9
Fix lints, remove ref to extensionAPI unit, add perf trace test
weswigham Jul 2, 2016
dad1bbe
Remove TS from extension error baselines, maybe deterministic profili…
weswigham Jul 2, 2016
9405dd3
Make extension profiling be a two-level process
weswigham Jul 5, 2016
5c97262
Always print global perf buckets (if any are present)
weswigham Jul 5, 2016
9f10c74
Merge branch 'master' into extensibility-model
weswigham Jul 5, 2016
625e3cf
Resolve paths early - stop considering all files in cache as root files
weswigham Jul 5, 2016
ed12a9c
Do not retain programs from compiler extension compilations
weswigham Jul 6, 2016
a011aec
Merge branch 'language-service-extensions' into extensibility-model
weswigham Jul 6, 2016
b4fc76c
Actually accept the baselines for the LS extension tests
weswigham Jul 6, 2016
1582d10
FourSlash tests for ExtensionRunner basic functionality
weswigham Jul 7, 2016
b5b7817
Semicolon.
weswigham Jul 7, 2016
84c683e
Add meaningful fourslash test covering much of the LS extension surface
weswigham Jul 7, 2016
ee23587
Merge branch 'master' into extensibility-model
weswigham Jul 7, 2016
5d2618b
Fix comments by @sandersn
weswigham Jul 12, 2016
67063cc
Convert kind anotations to actual kind overrides
weswigham Jul 12, 2016
50735ae
Add after visit, more error overloads, more robust error handling
weswigham Jul 12, 2016
732da00
Merge branch 'master' into extensibility-model
weswigham Jul 15, 2016
eab3c5a
Feedback from pr
weswigham Jul 15, 2016
86fcfb4
Reexpose startsWith and endsWith
weswigham Jul 15, 2016
e173bc6
PR feedback
weswigham Jul 15, 2016
4676ca2
Merge branch 'master' into extensibility-model
weswigham Jul 20, 2016
b8ca16a
Use new performance framework, remove extra compiler option, traces
weswigham Jul 20, 2016
563d71f
Merge branch 'master' into extensibility-model
weswigham Jul 20, 2016
ae66e99
Cleanup performLintPass from feedback from @sandersn
weswigham Jul 21, 2016
842d3c3
Fix typo
weswigham Jul 21, 2016
805bc2f
Merge branch 'master' into extensibility-model
weswigham Aug 1, 2016
40b35c4
Fix baselines with new AST, semantic lint test uses type instead of node
weswigham Aug 1, 2016
e5c342a
Make filters always run
weswigham Aug 1, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Functional extension loading & lint passes
  • Loading branch information
weswigham committed Jun 20, 2016
commit 8b3aeb8c43b24ee0210a602a9a716277a83ffb0c
5 changes: 3 additions & 2 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ var harnessSources = harnessCoreSources.concat([
"convertCompilerOptionsFromJson.ts",
"convertTypingOptionsFromJson.ts",
"tsserverProjectSystem.ts",
"matchFiles.ts"
"matchFiles.ts",
"extensionAPI.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
Expand Down Expand Up @@ -527,7 +528,7 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca

// Node package definition file to be distributed without the package. Created by replacing
// 'ts' namespace with '"typescript"' as a module.
var nodeStandaloneDefinitionsFileContents = definitionFileContents.replace(/declare (namespace|module) ts/g, 'declare module "typescript"');
var nodeStandaloneDefinitionsFileContents = definitionFileContents.replace(/declare (namespace|module) ts {/g, 'declare module "typescript" {\n import * as ts from "typescript";');
fs.writeFileSync(nodeStandaloneDefinitionsFile, nodeStandaloneDefinitionsFileContents);
});

Expand Down
4 changes: 4 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17258,6 +17258,10 @@ namespace ts {
return getTypeForVariableLikeDeclaration(<VariableLikeDeclaration>node.parent, /*includeOptionality*/ true);
}

if (node.kind === SyntaxKind.SourceFile) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the file is a module, we should return the module symbol.

return unknownType;
}

if (isInRightSideOfImportOrExportAssignment(<Identifier>node)) {
const symbol = getSymbolAtLocation(node);
const declaredType = symbol && getDeclaredTypeOfSymbol(symbol);
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ namespace ts {
experimental: true,
description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators
},
{
name: "extensions",
type: "object",
isTSConfigOnly: true,
description: Diagnostics.List_of_compiler_extensions_to_require
},
{
name: "moduleResolution",
type: {
Expand Down
20 changes: 20 additions & 0 deletions src/compiler/core.ts
6D40
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ namespace ts {
return array1.concat(array2);
}

export function flatten<T>(array1: T[][]): T[] {
if (!array1 || !array1.length) return <any>array1;
return [].concat(...array1);
}

export function groupBy<T>(array: T[], classifier: (item: T) => string): {[index: string]: T[]};
export function groupBy<T>(array: T[], classifier: (item: T) => number): {[index: number]: T[]};
export function groupBy<T>(array: T[], classifier: (item: T) => (string | number)): {[index: string]: T[], [index: number]: T[]} {
if (!array || !array.length) return undefined;
const ret: {[index: string]: T[], [index: number]: T[]} = {};
for (let i = 0, len = array.length; i < len; i++) {
Copy link
Member
@sandersn sandersn Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for-of here? #Resolved

const result = classifier(array[i]);
Copy link
Member
@sandersn sandersn Jul 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use the name key instead of result. #Resolved

if (!ret[result]) {
ret[result] = [];
}
ret[result].push(array[i]);
}
return ret;
}

export function deduplicate<T>(array: T[], areEqual?: (a: T, b: T) => boolean): T[] {
let result: T[];
if (array) {
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2656,7 +2656,7 @@
"category": "Message",
"code": 6099
},
"'package.json' does not have 'types' field.": {
"'package.json' does not have '{0}' field.": {
"category": "Message",
"code": 6100
},
Expand Down Expand Up @@ -2789,6 +2789,19 @@
"code": 6132
},

"List of compiler extensions to require.": {
"category": "Message",
"code": 6150
},
"Extension loading failed with error '{0}'.": {
"category": "Error",
"code": 6151
},
"Extension '{0}' exported member '{1}' has extension kind '{2}', but was type '{3}' when type '{4}' was expected.": {
"category": "Error",
"code": 6152
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
219 changes: 204 additions & 15 deletions src/compiler/program.ts

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace ts {
getMemoryUsage?(): number;
exit(exitCode?: number): void;
realpath?(path: string): string;
loadExtension?(name: string): any;
}

export interface FileWatcher {
Expand Down Expand Up @@ -547,6 +548,9 @@ namespace ts {
},
realpath(path: string): string {
return _fs.realpathSync(path);
},
loadExtension(name) {
return require(name);
}
};
}
Expand Down
18 changes: 13 additions & 5 deletions src/compiler/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ namespace ts {
}

const category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
if (diagnostic.category === DiagnosticCategory.Extension) {
output += `${ category } ${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we report the name of the extension that reported the error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is actually what diagnostic.code is for extensions.

}
else {
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
}

sys.write(output);
}
Expand Down Expand Up @@ -604,13 +609,16 @@ namespace ts {
// First get and report any syntactic errors.
diagnostics = program.getSyntacticDiagnostics();

// Count extension diagnostics and ignore them for determining continued error reporting
const extensionDiagnostics = filter(diagnostics, d => d.category === DiagnosticCategory.Extension).length;

// If we didn't have any syntactic errors, then also try getting the global and
// semantic errors.
if (diagnostics.length === 0) {
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
if (diagnostics.length === extensionDiagnostics) {
diagnostics = diagnostics.concat(program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()));

if (diagnostics.length === 0) {
diagnostics = program.getSemanticDiagnostics();
if (diagnostics.length === extensionDiagnostics) {
diagnostics = diagnostics.concat(program.getSemanticDiagnostics());
}
}

Expand Down
69 changes: 68 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,11 @@ namespace ts {
*/
getTypeChecker(): TypeChecker;

/**
* Gets a map of loaded compiler extensions
*/
getCompilerExtensions(): ExtensionCollectionMap;

/* @internal */ getCommonSourceDirectory(): string;

// For testing purposes only. Should not be used by any other consumers (including the
Expand Down Expand Up @@ -1812,6 +1817,7 @@ namespace ts {
getSourceFiles(): SourceFile[];
getSourceFile(fileName: string): SourceFile;
getResolvedTypeReferenceDirectives(): Map<ResolvedTypeReferenceDirective>;
getCompilerExtensions(): ExtensionCollectionMap;
}

export interface TypeChecker {
Expand Down Expand Up @@ -2496,13 +2502,14 @@ namespace ts {
length: number;
messageText: string | DiagnosticMessageChain;
category: DiagnosticCategory;
code: number;
code: number | string;
}

export enum DiagnosticCategory {
Warning,
Error,
Message,
Extension,
}

export enum ModuleResolutionKind {
Expand Down Expand Up @@ -2586,6 +2593,7 @@ namespace ts {
typesSearchPaths?: string[];
/*@internal*/ version?: boolean;
/*@internal*/ watch?: boolean;
extensions?: string[] | Map<any>;

[option: string]: CompilerOptionsValue | undefined;
}
Expand Down Expand Up @@ -2893,6 +2901,57 @@ namespace ts {
failedLookupLocations: string[];
}

export type LintErrorMethod = (err: string, span: Node) => void;
export type LintAcceptMethod = () => void;

/*
* Walkers call accept to decend into the node's children
* Walkers call error to add errors to the output.
*/
export interface LintWalker {
visit(node: Node, accept: LintAcceptMethod, error: LintErrorMethod): void;
}

export interface SyntacticLintProviderStatic {
new (typescript: typeof ts, args: any): LintWalker;
}

export interface SemanticLintProviderStatic {
new (typescript: typeof ts, checker: TypeChecker, args: any): LintWalker;
}

export namespace ExtensionKind {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1241

Do we really need this pseudo-string-valued-enum? Can we just use the string literals themselves to avoid having to look up this value on ExtensionKind at runtime when a string literal would be more efficient?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String literals don't get completions in value positions; namespace members do (and furthermore the namespace helps unify and organize them, much like enums). If you're suggesting we inline them, then maybe we should implement generic constant in-lining. ;)

Plus, access to them never happens on a particularly hot code path, so the perf difference should't matter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String literals don't get completions in value positions; namespace members do (and furthermore the namespace helps unify and organize them, much like enums). If you're suggesting we inline them, then maybe we should implement generic constant in-lining. ;)

Or, maybe we should consider completions for string literals?

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 type ExtensionKind = ExtensionKind.SemanticLint | ExtensionKind.SyntacticLint;

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

export interface ExtensionBase {
name: string;
args: any;
kind: ExtensionKind;
}

// @kind(ExtensionKind.SyntacticLint)
export interface SyntacticLintExtension extends ExtensionBase {
ctor: SyntacticLintProviderStatic;
}

// @kind(ExtensionKind.SemanticLint)
export interface SemanticLintExtension extends ExtensionBase {
ctor: SemanticLintProviderStatic;
}

export type Extension = SyntacticLintExtension | SemanticLintExtension;

export interface CompilerHost extends ModuleResolutionHost {
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
Expand All @@ -2919,6 +2978,14 @@ namespace ts {
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
*/
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];

/**
* Delegates the loading of compiler extensions to the compiler host.
* The function should return the result of executing the code of an extension
* - its exported members. These members will be searched for objects who have been decorated with
* specific flags.
*/
loadExtension?(extension: string): any;
}

export interface TextSpan {
Expand Down
13 changes: 13 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,19 @@ namespace ts {
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2);
}

export function createExtensionDiagnosticForNode(node: Node, extension: string, message: string): Diagnostic {
const sourceFile = getSourceFileOfNode(node);
const span = getErrorSpanForNode(sourceFile, node);
return {
file: sourceFile,
messageText: message,
code: extension,
start: span.start,
length: span.length,
category: DiagnosticCategory.Extension
};
}

export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
const sourceFile = getSourceFileOfNode(node);
const span = getErrorSpanForNode(sourceFile, node);
Expand Down
4 changes: 2 additions & 2 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,11 +541,11 @@ namespace ts {
}
}

export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number; }[] {
export function realizeDiagnostics(diagnostics: Diagnostic[], newLine: string): { message: string; start: number; length: number; category: string; code: number | string; }[] {
return diagnostics.map(d => realizeDiagnostic(d, newLine));
}

function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number; } {
function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number | string; } {
return {
message: flattenDiagnosticMessageText(diagnostic.messageText, newLine),
start: diagnostic.start,
Expand Down
Loading
0