Skip to content

Commit 8b3aeb8

Browse files
committed
Functional extension loading & lint passes
1 parent 6d99b2f commit 8b3aeb8

File tree

12 files changed

+806
-26
lines changed

12 files changed

+806
-26
lines changed

Jakefile.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ var harnessSources = harnessCoreSources.concat([
157157
"convertCompilerOptionsFromJson.ts",
158158
"convertTypingOptionsFromJson.ts",
159159
"tsserverProjectSystem.ts",
160-
"matchFiles.ts"
160+
"matchFiles.ts",
161+
"extensionAPI.ts",
161162
].map(function (f) {
162163
return path.join(unittestsDirectory, f);
163164
})).concat([
@@ -527,7 +528,7 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca
527528

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

src/compiler/checker.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17258,6 +17258,10 @@ namespace ts {
1725817258
return getTypeForVariableLikeDeclaration(<VariableLikeDeclaration>node.parent, /*includeOptionality*/ true);
1725917259
}
1726017260

17261+
if (node.kind === SyntaxKind.SourceFile) {
17262+
return unknownType;
17263+
}
17264+
1726117265
if (isInRightSideOfImportOrExportAssignment(<Identifier>node)) {
1726217266
const symbol = getSymbolAtLocation(node);
1726317267
const declaredType = symbol && getDeclaredTypeOfSymbol(symbol);

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,12 @@ namespace ts {
268268
experimental: true,
269269
description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators
270270
},
271+
{
272+
name: "extensions",
273+
type: "object",
274+
isTSConfigOnly: true,
275+
description: Diagnostics.List_of_compiler_extensions_to_require
276+
},
271277
{
272278
name: "moduleResolution",
273279
type: {

src/compiler/core.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,26 @@ namespace ts {
176176
return array1.concat(array2);
177177
}
178178

179+
export function flatten<T>(array1: T[][]): T[] {
180+
if (!array1 || !array1.length) return <any>array1;
181+
return [].concat(...array1);
182+
}
183+
184+
export function groupBy<T>(array: T[], classifier: (item: T) => string): {[index: string]: T[]};
185+
export function groupBy<T>(array: T[], classifier: (item: T) => number): {[index: number]: T[]};
186+
export function groupBy<T>(array: T[], classifier: (item: T) => (string | number)): {[index: string]: T[], [index: number]: T[]} {
187+
if (!array || !array.length) return undefined;
188+
const ret: {[index: string]: T[], [index: number]: T[]} = {};
189+
for (let i = 0, len = array.length; i < len; i++) {
190+
const result = classifier(array[i]);
191+
if (!ret[result]) {
192+
ret[result] = [];
193+
}
194+
ret[result].push(array[i]);
195+
}
196+
return ret;
197+
}
198+
179199
export function deduplicate<T>(array: T[], areEqual?: (a: T, b: T) => boolean): T[] {
180200
let result: T[];
181201
if (array) {

src/compiler/diagnosticMessages.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2656,7 +2656,7 @@
26562656
"category": "Message",
26572657
"code": 6099
26582658
},
2659-
"'package.json' does not have 'types' field.": {
2659+
"'package.json' does not have '{0}' field.": {
26602660
"category": "Message",
26612661
"code": 6100
26622662
},
@@ -2789,6 +2789,19 @@
27892789
"code": 6132
27902790
},
27912791

2792+
"List of compiler extensions to require.": {
2793+
"category": "Message",
2794+
"code": 6150
2795+
},
2796+
"Extension loading failed with error '{0}'.": {
2797+
"category": "Error",
2798+
"code": 6151
2799+
},
2800+
"Extension '{0}' exported member '{1}' has extension kind '{2}', but was type '{3}' when type '{4}' was expected.": {
2801+
"category": "Error",
2802+
"code": 6152
2803+
},
2804+
27922805
"Variable '{0}' implicitly has an '{1}' type.": {
27932806
"category": "Error",
27942807
"code": 7005

src/compiler/program.ts

Lines changed: 204 additions & 15 deletions
Large diffs are not rendered by default.

src/compiler/sys.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace ts {
3232
getMemoryUsage?(): number;
3333
exit(exitCode?: number): void;
3434
realpath?(path: string): string;
35+
loadExtension?(name: string): any;
3536
}
3637

3738
export interface FileWatcher {
@@ -547,6 +548,9 @@ namespace ts {
547548
},
548549
realpath(path: string): string {
549550
return _fs.realpathSync(path);
551+
},
552+
loadExtension(name) {
553+
return require(name);
550554
}
551555
};
552556
}

src/compiler/tsc.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,12 @@ namespace ts {
115115
}
116116

117117
const category = DiagnosticCategory[diagnostic.category].toLowerCase();
118-
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
118+
if (diagnostic.category === DiagnosticCategory.Extension) {
119+
output += `${ category } ${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
120+
}
121+
else {
122+
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
123+
}
119124

120125
sys.write(output);
121126
}
@@ -604,13 +609,16 @@ namespace ts {
604609
// First get and report any syntactic errors.
605610
diagnostics = program.getSyntacticDiagnostics();
606611

612+
// Count extension diagnostics and ignore them for determining continued error reporting
613+
const extensionDiagnostics = filter(diagnostics, d => d.category === DiagnosticCategory.Extension).length;
614+
607615
// If we didn't have any syntactic errors, then also try getting the global and
608616
// semantic errors.
609-
if (diagnostics.length === 0) {
610-
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
617+
if (diagnostics.length === extensionDiagnostics) {
618+
diagnostics = diagnostics.concat(program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics()));
611619

612-
if (diagnostics.length === 0) {
613-
diagnostics = program.getSemanticDiagnostics();
620+
if (diagnostics.length === extensionDiagnostics) {
621+
diagnostics = diagnostics.concat(program.getSemanticDiagnostics());
614622
}
615623
}
616624

src/compiler/types.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,11 @@ namespace ts {
17361736
*/
17371737
getTypeChecker(): TypeChecker;
17381738

1739+
/**
1740+
* Gets a map of loaded compiler extensions
1741+
*/
1742+
getCompilerExtensions(): ExtensionCollectionMap;
1743+
17391744
/* @internal */ getCommonSourceDirectory(): string;
17401745

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

18171823
export interface TypeChecker {
@@ -2496,13 +2502,14 @@ namespace ts {
24962502
length: number;
24972503
messageText: string | DiagnosticMessageChain;
24982504
category: DiagnosticCategory;
2499-
code: number;
2505+
code: number | string;
25002506
}
25012507

25022508
export enum DiagnosticCategory {
25032509
Warning,
25042510
Error,
25052511
Message,
2512+
Extension,
25062513
}
25072514

25082515
export enum ModuleResolutionKind {
@@ -2586,6 +2593,7 @@ namespace ts {
25862593
typesSearchPaths?: string[];
25872594
/*@internal*/ version?: boolean;
25882595
/*@internal*/ watch?: boolean;
2596+
extensions?: string[] | Map<any>;
25892597

25902598
[option: string]: CompilerOptionsValue | undefined;
25912599
}
@@ -2893,6 +2901,57 @@ namespace ts {
28932901
failedLookupLocations: string[];
28942902
}
28952903

2904+
export type LintErrorMethod = (err: string, span: Node) => void;
2905+
export type LintAcceptMethod = () => void;
2906+
2907+
/*
2908+
* Walkers call accept to decend into the node's children
2909+
* Walkers call error to add errors to the output.
2910+
*/
2911+
export interface LintWalker {
2912+
visit(node: Node, accept: LintAcceptMethod, error: LintErrorMethod): void;
2913+
}
2914+
2915+
export interface SyntacticLintProviderStatic {
2916+
new (typescript: typeof ts, args: any): LintWalker;
2917+
}
2918+
2919+
export interface SemanticLintProviderStatic {
2920+
new (typescript: typeof ts, checker: TypeChecker, args: any): LintWalker;
2921+
}
2922+
2923+
export namespace ExtensionKind {
2924+
export const SemanticLint: "semantic-lint" = "semantic-lint";
2925+
export type SemanticLint = "semantic-lint";
2926+
export const SyntacticLint: "syntactic-lint" = "syntactic-lint";
2927+
export type SyntacticLint = "syntactic-lint";
2928+
}
2929+
export type ExtensionKind = ExtensionKind.SemanticLint | ExtensionKind.SyntacticLint;
2930+
2931+
export interface ExtensionCollectionMap {
2932+
"syntactic-lint"?: SyntacticLintExtension[];
2933+
"semantic-lint"?: SemanticLintExtension[];
2934+
[index: string]: Extension[] | undefined;
2935+
}
2936+
2937+
export interface ExtensionBase {
2938+
name: string;
2939+
args: any;
2940+
kind: ExtensionKind;
2941+
}
2942+
2943+
// @kind(ExtensionKind.SyntacticLint)
2944+
export interface SyntacticLintExtension extends ExtensionBase {
2945+
ctor: SyntacticLintProviderStatic;
2946+
}
2947+
2948+
// @kind(ExtensionKind.SemanticLint)
2949+
export interface SemanticLintExtension extends ExtensionBase {
2950+
ctor: SemanticLintProviderStatic;
2951+
}
2952+
2953+
export type Extension = SyntacticLintExtension | SemanticLintExtension;
2954+
28962955
export interface CompilerHost extends ModuleResolutionHost {
28972956
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
28982957
getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile;
@@ -2919,6 +2978,14 @@ namespace ts {
29192978
* This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files
29202979
*/
29212980
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
2981+
2982+
/**
2983+
* Delegates the loading of compiler extensions to the compiler host.
2984+
* The function should return the result of executing the code of an extension
2985+
* - its exported members. These members will be searched for objects who have been decorated with
2986+
* specific flags.
2987+
*/
2988+
loadExtension?(extension: string): any;
29222989
}
29232990

29242991
export interface TextSpan {

src/compiler/utilities.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,19 @@ namespace ts {
453453
return createFileDiagnostic(sourceFile, span.start, span.length, message, arg0, arg1, arg2);
454454
}
455455

456+
export function createExtensionDiagnosticForNode(node: Node, extension: string, message: string): Diagnostic {
457+
const sourceFile = getSourceFileOfNode(node);
458+
const span = getErrorSpanForNode(sourceFile, node);
459+
return {
460+
file: sourceFile,
461+
messageText: message,
462+
code: extension,
463+
start: span.start,
464+
length: span.length,
465+
category: DiagnosticCategory.Extension
466+
};
467+
}
468+
456469
export function createDiagnosticForNodeFromMessageChain(node: Node, messageChain: DiagnosticMessageChain): Diagnostic {
457470
const sourceFile = getSourceFileOfNode(node);
458471
const span = getErrorSpanForNode(sourceFile, node);

src/services/shims.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,11 +541,11 @@ namespace ts {
541541
}
542542
}
543543

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

548-
function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number; } {
548+
function realizeDiagnostic(diagnostic: Diagnostic, newLine: string): { message: string; start: number; length: number; category: string; code: number | string; } {
549549
return {
550550
message: flattenDiagnosticMessageText(diagnostic.messageText, newLine),
551551
start: diagnostic.start,

0 commit comments

Comments
 (0)