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: Extensibility model #9038

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from 52 commits
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
2 changes: 1 addition & 1 deletion Gulpfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ gulp.task(servicesFile, false, ["lib", "generate-diagnostics"], () => {
completedDts.pipe(clone())
.pipe(insert.transform((content, file) => {
file.path = nodeStandaloneDefinitionsFile;
return content.replace(/declare (namespace|module) ts/g, 'declare module "typescript"');
return content.replace(/declare (namespace|module) ts {/g, 'declare module "typescript" {\n import * as ts from "typescript";');
}))
]).pipe(gulp.dest(builtLocalDirectory));
});
Expand Down
7 changes: 5 additions & 2 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ var compilerSources = [
"declarationEmitter.ts",
"emitter.ts",
"program.ts",
"extensions.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
Expand All @@ -65,6 +66,7 @@ var servicesSources = [
"declarationEmitter.ts",
"emitter.ts",
"program.ts",
"extensions.ts",
"commandLineParser.ts",
"diagnosticInformationMap.generated.ts"
].map(function (f) {
Expand Down Expand Up @@ -129,6 +131,7 @@ var harnessCoreSources = [
"typeWriter.ts",
"fourslashRunner.ts",
"projectsRunner.ts",
"extensionRunner.ts",
"loggedIO.ts",
"rwcRunner.ts",
"test262Runner.ts",
Expand Down Expand Up @@ -156,7 +159,7 @@ var harnessSources = harnessCoreSources.concat([
"convertCompilerOptionsFromJson.ts",
"convertTypingOptionsFromJson.ts",
"tsserverProjectSystem.ts",
"matchFiles.ts"
"matchFiles.ts",
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
Expand Down Expand Up @@ -531,7 +534,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: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17531,9 +17531,9 @@ namespace ts {
return getTypeOfSymbol(symbol);
}

if (isDeclarationName(node)) {
if (isDeclarationName(node) || node.kind === SyntaxKind.SourceFile) {
const symbol = getSymbolAtLocation(node);
return symbol && getTypeOfSymbol(symbol);
return symbol && getTypeOfSymbol(symbol) || unknownType;
}

if (isBindingPattern(node)) {
Expand Down
15 changes: 15 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,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 Expand Up @@ -425,6 +431,15 @@ namespace ts {
name: "strictNullChecks",
type: "boolean",
description: Diagnostics.Enable_strict_null_checks
},
{
name: "profileExtensions",
type: {
"none": ProfileLevel.None,
"summary": ProfileLevel.Summary,
"full": ProfileLevel.Full,
},
description: Diagnostics.Set_extension_profiling_level_Colon,
}
];

Expand Down
68 changes: 67 additions & 1 deletion src/compiler/core.ts
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 (const elem of array) {
const key = classifier(elem);
if (!ret[key]) {
ret[key] = [];
}
ret[key].push(elem);
}
return ret;
}

export function deduplicate<T>(array: T[], areEqual?: (a: T, b: T) => boolean): T[] {
let result: T[];
if (array) {
Expand Down Expand Up @@ -1146,7 +1166,8 @@ namespace ts {
export const supportedJavascriptExtensions = [".js", ".jsx"];
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);

export function getSupportedExtensions(options?: CompilerOptions): string[] {
export function getSupportedExtensions(options?: CompilerOptions, loadJS?: boolean): string[] {
if (loadJS) return supportedJavascriptExtensions;
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
}

Expand Down Expand Up @@ -1324,4 +1345,49 @@ namespace ts {
: ((fileName) => fileName.toLowerCase());
}

/**
* This isn't the strictest deep equal, but it's good enough for us
* - +0 === -0 (though who really wants to consider them different?)
* - arguments and arrays can be equal (both typeof === object, both have enumerable keys)
* - doesn't inspect es6 iterables (not that they're used in this code base)
* - doesn't inspect regex toString value (so only references to the same regex are equal)
* - doesn't inspect date primitive number value (so only references to the same date are equal)
*/
export function deepEqual(a: any, b: any, memo?: [any, any][]): boolean {
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.

do we really need all the features that deepEqual provides for its single use? I feel like it would be safer to put a utility in services that does only the equality that is needed there, and only generalise it as we find new uses for it. #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

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

Extension arguments objects can be literally any object, and that's what's getting compared. I think the only thing this handles which I shouldn't (provided good input from any language service wrappers or other API consumers) need to worry about for that is circularly referential objects. (Which is why all the shortcomings listed in the comment were OK)

if (a === b) return true;
if (typeof a !== typeof b) return false;
// Special case NaN
if (typeof a === "number" && isNaN(a) && isNaN(b)) return true;
// We can't know if function arguments are deep equal, so we say they're equal if they look alike
if (typeof a === "object" || typeof a === "function") {
if (memo) {
for (let i = 0; i < memo.length; i++) {
if (memo[i][0] === a && memo[i][1] === b) return true;
if (memo[i][0] === b && memo[i][1] === a) return true;
}
}
else {
memo = [];
}

const aKeys = ts.getKeys(a);
const bKeys = ts.getKeys(b);
aKeys.sort();
bKeys.sort();

if (aKeys.length !== bKeys.length) return false;

for (let i = 0; i < aKeys.length; i++) {
if (aKeys[i] !== bKeys[i]) return false;
}

memo.push([a, b]);

for (const key of aKeys) {
if (!deepEqual(a[key], b[key], memo)) return false;
}
return true;
}
return false;
}
}
30 changes: 26 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2664,7 +2664,7 @@
"category": "Message",
"code": 6099
},
"'package.json' does not have 'types' field.": {
"'package.json' does not have '{0}' field.": {
"category": "Message",
"code": 6100
},
Expand All @@ -2684,7 +2684,7 @@
"category": "Message",
"code": 6104
},
"Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": {
"Expected type of '{0}' field in 'package.json' to be '{1}', got '{2}'.": {
"category": "Message",
"code": 6105
},
Expand Down Expand Up @@ -2812,10 +2812,32 @@
"category": "Message",
"code": 6136
},
"No types specified in 'package.json' but 'allowJs' is set, so returning 'main' value of '{0}'": {

"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
},
"Set extension profiling level:": {
"category": "Message",
"code": 6153
},
"PROFILE: Extension '{0}' begin '{1}'": {
"category": "Message",
"code": 6137
"code": 6154
},
"PROFILE: Extension '{0}' end '{1}' ({2} ms)": {
"category": "Message",
"code": 6155
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005
Expand Down
Loading