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

Add wrapper to emit statics/decorators inside es5 class #16120

Merged
merged 7 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
98 changes: 77 additions & 21 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,18 +752,34 @@ namespace ts {
return to;
}

/**
* Gets the actual offset into an array for a relative offset. Negative offsets indicate a
* position offset from the end of the array.
*/
function toOffset(array: any[], offset: number) {
return offset < 0 ? array.length + offset : offset;
}

/**
* Appends a range of value to an array, returning the array.
*
* @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array
* is created if `value` was appended.
* @param from The values to append to the array. If `from` is `undefined`, nothing is
* appended. If an element of `from` is `undefined`, that element is not appended.
* @param start The offset in `from` at which to start copying values.
* @param end The offset in `from` at which to stop copying values (non-inclusive).
*/
export function addRange<T>(to: T[] | undefined, from: T[] | undefined): T[] | undefined {
export function addRange<T>(to: T[] | undefined, from: T[] | undefined, start?: number, end?: number): T[] | undefined {
if (from === undefined) return to;
for (const v of from) {
to = append(to, v);
if (to === undefined) return from.slice(start, end);
start = start === undefined ? 0 : toOffset(from, start);
end = end === undefined ? from.length : toOffset(from, end);
for (let i = start; i < end && i < from.length; i++) {
const v = from[i];
if (v !== undefined) {
to.push(from[i]);
}
}
return to;
}
Expand All @@ -788,28 +804,38 @@ namespace ts {
return true;
}

/**
* Returns the element at a specific offset in an array if non-empty, `undefined` otherwise.
* A negative offset indicates the element should be retrieved from the end of the array.
*/
export function elementAt<T>(array: T[] | undefined, offset: number): T | undefined {
if (array) {
offset = toOffset(array, offset);
if (offset < array.length) {
return array[offset];
}
}
return undefined;
}

/**
* Returns the first element of an array if non-empty, `undefined` otherwise.
*/
export function firstOrUndefined<T>(array: T[]): T {
return array && array.length > 0
? array[0]
: undefined;
export function firstOrUndefined<T>(array: T[]): T | undefined {
return elementAt(array, 0);
}

/**
* Returns the last element of an array if non-empty, `undefined` otherwise.
*/
export function lastOrUndefined<T>(array: T[]): T {
return array && array.length > 0
? array[array.length - 1]
: undefined;
export function lastOrUndefined<T>(array: T[]): T | undefined {
return elementAt(array, -1);
}

/**
* Returns the only element of an array if it contains only one element, `undefined` otherwise.
*/
export function singleOrUndefined<T>(array: T[]): T {
export function singleOrUndefined<T>(array: T[]): T | undefined {
return array && array.length === 1
? array[0]
: undefined;
Expand Down Expand Up @@ -1137,6 +1163,15 @@ namespace ts {
return Array.isArray ? Array.isArray(value) : value instanceof Array;
}

export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
return value !== undefined && test(value) ? value : undefined;
}

export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut {
Copy link

Choose a reason for hiding this comment

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

Don't see any reason to allow an undefined parameter since the function will always fail in that case.

Copy link
Member Author

@rbuckton rbuckton May 30, 2017

Choose a reason for hiding this comment

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

If we ever turn on --strictNullChecks there may be cases where value may be typed undefined.

if (value !== undefined && test(value)) return value;
Debug.fail(`Invalid cast. The supplied value did not pass the test '${Debug.getFunctionName(test)}'.`);
}

/** Does nothing. */
export function noop(): void {}

Expand Down Expand Up @@ -2222,8 +2257,11 @@ namespace ts {
this.declarations = undefined;
}

function Type(this: Type, _checker: TypeChecker, flags: TypeFlags) {
function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {
this.flags = flags;
if (Debug.isDebugging) {
this.checker = checker;
}
}

function Signature() {
Expand Down Expand Up @@ -2260,24 +2298,42 @@ namespace ts {

export namespace Debug {
export let currentAssertionLevel = AssertionLevel.None;
export let isDebugging = false;

export function shouldAssert(level: AssertionLevel): boolean {
return currentAssertionLevel >= level;
}

export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string): void {
export function assert(expression: boolean, message?: string, verboseDebugInfo?: () => string, stackCrawlMark?: Function): void {
if (!expression) {
let verboseDebugString = "";
if (verboseDebugInfo) {
verboseDebugString = "\r\nVerbose Debug Information: " + verboseDebugInfo();
message += "\r\nVerbose Debug Information: " + verboseDebugInfo();
}
debugger;
throw new Error("Debug Failure. False expression: " + (message || "") + verboseDebugString);
fail(message ? "False expression: " + message : "False expression.", stackCrawlMark || assert);
}
}

export function fail(message?: string, stackCrawlMark?: Function): void {
debugger;
const e = new Error(message ? `Debug Failure. ` : "Debug Failure.");
if ((<any>Error).captureStackTrace) {
(<any>Error).captureStackTrace(e, stackCrawlMark || fail);
}
throw e;
}

export function fail(message?: string): void {
Debug.assert(/*expression*/ false, message);
export function getFunctionName(func: Function) {
if (typeof func !== "function") {
return "";
}
else if (func.hasOwnProperty("name")) {
return (<any>func).name;
}
else {
const text = Function.prototype.toString.call(func);
const match = /^function\s+([\w\$]+)\s*\(/.exec(text);
return match ? match[1] : "";
}
}
}

Expand Down Expand Up @@ -2443,4 +2499,4 @@ namespace ts {
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
}
}
}
96 changes: 77 additions & 19 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,24 @@ namespace ts {

// Compound nodes

export function createImmediatelyInvokedFunctionExpression(statements: Statement[]): CallExpression;
export function createImmediatelyInvokedFunctionExpression(statements: Statement[], param: ParameterDeclaration, paramValue: Expression): CallExpression;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should param and paramValue be default value of []?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, these are not arrays. As written, this function only allows zero or one parameter.

export function createImmediatelyInvokedFunctionExpression(statements: Statement[], param?: ParameterDeclaration, paramValue?: Expression) {
return createCall(
createFunctionExpression(
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ param ? [param] : [],
/*type*/ undefined,
createBlock(statements, /*multiLine*/ true)
),
/*typeArguments*/ undefined,
/*argumentsArray*/ paramValue ? [paramValue] : []
);
}

export function createComma(left: Expression, right: Expression) {
return <Expression>createBinary(left, SyntaxKind.CommaToken, right);
}
Expand Down Expand Up @@ -3216,6 +3234,26 @@ namespace ts {
return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node);
}

export function convertFunctionDeclarationToExpression(node: FunctionDeclaration) {
Debug.assert(!!node.body);
const updated = createFunctionExpression(
node.modifiers,
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
node.body
);
setOriginalNode(updated, node);
setTextRange(updated, node);
if (node.startsOnNewLine) {
updated.startsOnNewLine = true;
}
aggregateTransformFlags(updated);
return updated;
}

function isUseStrictPrologue(node: ExpressionStatement): boolean {
return (node.expression as StringLiteral).text === "use strict";
}
Expand Down Expand Up @@ -3614,7 +3652,7 @@ namespace ts {
if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) {
const mutableCall = getMutableClone(emittedExpression);
mutableCall.expression = setTextRange(createParen(callee), callee);
return recreatePartiallyEmittedExpressions(expression, mutableCall);
return recreateOuterExpressions(expression, mutableCall, OuterExpressionKinds.PartiallyEmittedExpressions);
}
}
else {
Expand Down Expand Up @@ -3656,22 +3694,6 @@ namespace ts {
}
}

/**
* Clones a series of not-emitted expressions with a new inner expression.
*
* @param originalOuterExpression The original outer expression.
* @param newInnerExpression The new inner expression.
*/
function recreatePartiallyEmittedExpressions(originalOuterExpression: Expression, newInnerExpression: Expression) {
if (isPartiallyEmittedExpression(originalOuterExpression)) {
const clone = getMutableClone(originalOuterExpression);
clone.expression = recreatePartiallyEmittedExpressions(clone.expression, newInnerExpression);
return clone;
}

return newInnerExpression;
}

function getLeftmostExpression(node: Expression): Expression {
while (true) {
switch (node.kind) {
Expand Down Expand Up @@ -3718,6 +3740,22 @@ namespace ts {
All = Parentheses | Assertions | PartiallyEmittedExpressions
}

export type OuterExpression = ParenthesizedExpression | TypeAssertion | AsExpression | NonNullExpression | PartiallyEmittedExpression;

export function isOuterExpression(node: Node, kinds = OuterExpressionKinds.All): node is OuterExpression {
switch (node.kind) {
case SyntaxKind.ParenthesizedExpression:
return (kinds & OuterExpressionKinds.Parentheses) !== 0;
case SyntaxKind.TypeAssertionExpression:
case SyntaxKind.AsExpression:
case SyntaxKind.NonNullExpression:
return (kinds & OuterExpressionKinds.Assertions) !== 0;
case SyntaxKind.PartiallyEmittedExpression:
return (kinds & OuterExpressionKinds.PartiallyEmittedExpressions) !== 0;
}
return false;
}

export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression;
export function skipOuterExpressions(node: Node, kinds?: OuterExpressionKinds): Node;
export function skipOuterExpressions(node: Node, kinds = OuterExpressionKinds.All) {
Expand Down Expand Up @@ -3754,8 +3792,8 @@ namespace ts {
export function skipAssertions(node: Expression): Expression;
export function skipAssertions(node: Node): Node;
export function skipAssertions(node: Node): Node {
while (isAssertionExpression(node)) {
node = (<AssertionExpression>node).expression;
while (isAssertionExpression(node) || node.kind === SyntaxKind.NonNullExpression) {
node = (<AssertionExpression | NonNullExpression>node).expression;
}

return node;
Expand All @@ -3771,6 +3809,26 @@ namespace ts {
return node;
}

function updateOuterExpression(outerExpression: OuterExpression, expression: Expression) {
switch (outerExpression.kind) {
case SyntaxKind.ParenthesizedExpression: return updateParen(outerExpression, expression);
case SyntaxKind.TypeAssertionExpression: return updateTypeAssertion(outerExpression, outerExpression.type, expression);
case SyntaxKind.AsExpression: return updateAsExpression(outerExpression, expression, outerExpression.type);
case SyntaxKind.NonNullExpression: return updateNonNullExpression(outerExpression, expression);
case SyntaxKind.PartiallyEmittedExpression: return updatePartiallyEmittedExpression(outerExpression, expression);
}
}

export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression {
if (outerExpression && isOuterExpression(outerExpression, kinds)) {
return updateOuterExpression(
outerExpression,
recreateOuterExpressions(outerExpression.expression, innerExpression)
);
}
return innerExpression;
}

export function startOnNewLine<T extends Node>(node: T): T {
node.startsOnNewLine = true;
return node;
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace ts {
realpath?(path: string): string;
/*@internal*/ getEnvironmentVariable(name: string): string;
/*@internal*/ tryEnableSourceMapsForHost?(): void;
/*@internal*/ debugMode?: boolean;
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout?(timeoutId: any): void;
}
Expand Down Expand Up @@ -428,6 +429,7 @@ namespace ts {
realpath(path: string): string {
return _fs.realpathSync(path);
},
debugMode: some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
tryEnableSourceMapsForHost() {
try {
require("source-map-support").install();
Expand Down Expand Up @@ -517,4 +519,7 @@ namespace ts {
? AssertionLevel.Normal
: AssertionLevel.None;
}
if (sys && sys.debugMode) {
Debug.isDebugging = true;
}
}
Loading