* Fix emit for optional chain with non-null assertion * Treat '!' differently inside chain vs end of chain * remove dead code and fix comment
1897 lines
84 KiB
TypeScript
1897 lines
84 KiB
TypeScript
/* @internal */
|
|
namespace ts {
|
|
export const nullTransformationContext: TransformationContext = {
|
|
enableEmitNotification: noop,
|
|
enableSubstitution: noop,
|
|
endLexicalEnvironment: returnUndefined,
|
|
getCompilerOptions: notImplemented,
|
|
getEmitHost: notImplemented,
|
|
getEmitResolver: notImplemented,
|
|
hoistFunctionDeclaration: noop,
|
|
hoistVariableDeclaration: noop,
|
|
isEmitNotificationEnabled: notImplemented,
|
|
isSubstitutionEnabled: notImplemented,
|
|
onEmitNode: noop,
|
|
onSubstituteNode: notImplemented,
|
|
readEmitHelpers: notImplemented,
|
|
requestEmitHelper: noop,
|
|
resumeLexicalEnvironment: noop,
|
|
startLexicalEnvironment: noop,
|
|
suspendLexicalEnvironment: noop,
|
|
addDiagnostic: noop,
|
|
};
|
|
|
|
// Compound nodes
|
|
|
|
export type TypeOfTag = "undefined" | "number" | "boolean" | "string" | "symbol" | "object" | "function";
|
|
|
|
export function createTypeCheck(value: Expression, tag: TypeOfTag) {
|
|
return tag === "undefined"
|
|
? createStrictEquality(value, createVoidZero())
|
|
: createStrictEquality(createTypeOf(value), createLiteral(tag));
|
|
}
|
|
|
|
export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression {
|
|
if (isComputedPropertyName(memberName)) {
|
|
return setTextRange(createElementAccess(target, memberName.expression), location);
|
|
}
|
|
else {
|
|
const expression = setTextRange(
|
|
(isIdentifier(memberName) || isPrivateIdentifier(memberName))
|
|
? createPropertyAccess(target, memberName)
|
|
: createElementAccess(target, memberName),
|
|
memberName
|
|
);
|
|
getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps;
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
export function createFunctionCall(func: Expression, thisArg: Expression, argumentsList: readonly Expression[], location?: TextRange) {
|
|
return setTextRange(
|
|
createCall(
|
|
createPropertyAccess(func, "call"),
|
|
/*typeArguments*/ undefined,
|
|
[
|
|
thisArg,
|
|
...argumentsList
|
|
]),
|
|
location
|
|
);
|
|
}
|
|
|
|
export function createFunctionApply(func: Expression, thisArg: Expression, argumentsExpression: Expression, location?: TextRange) {
|
|
return setTextRange(
|
|
createCall(
|
|
createPropertyAccess(func, "apply"),
|
|
/*typeArguments*/ undefined,
|
|
[
|
|
thisArg,
|
|
argumentsExpression
|
|
]
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
export function createArraySlice(array: Expression, start?: number | Expression) {
|
|
const argumentsList: Expression[] = [];
|
|
if (start !== undefined) {
|
|
argumentsList.push(typeof start === "number" ? createLiteral(start) : start);
|
|
}
|
|
|
|
return createCall(createPropertyAccess(array, "slice"), /*typeArguments*/ undefined, argumentsList);
|
|
}
|
|
|
|
export function createArrayConcat(array: Expression, values: readonly Expression[]) {
|
|
return createCall(
|
|
createPropertyAccess(array, "concat"),
|
|
/*typeArguments*/ undefined,
|
|
values
|
|
);
|
|
}
|
|
|
|
export function createMathPow(left: Expression, right: Expression, location?: TextRange) {
|
|
return setTextRange(
|
|
createCall(
|
|
createPropertyAccess(createIdentifier("Math"), "pow"),
|
|
/*typeArguments*/ undefined,
|
|
[left, right]
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
function createReactNamespace(reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment) {
|
|
// To ensure the emit resolver can properly resolve the namespace, we need to
|
|
// treat this identifier as if it were a source tree node by clearing the `Synthesized`
|
|
// flag and setting a parent node.
|
|
const react = createIdentifier(reactNamespace || "React");
|
|
react.flags &= ~NodeFlags.Synthesized;
|
|
// Set the parent that is in parse tree
|
|
// this makes sure that parent chain is intact for checker to traverse complete scope tree
|
|
react.parent = getParseTreeNode(parent);
|
|
return react;
|
|
}
|
|
|
|
function createJsxFactoryExpressionFromEntityName(jsxFactory: EntityName, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
|
|
if (isQualifiedName(jsxFactory)) {
|
|
const left = createJsxFactoryExpressionFromEntityName(jsxFactory.left, parent);
|
|
const right = createIdentifier(idText(jsxFactory.right));
|
|
right.escapedText = jsxFactory.right.escapedText;
|
|
return createPropertyAccess(left, right);
|
|
}
|
|
else {
|
|
return createReactNamespace(idText(jsxFactory), parent);
|
|
}
|
|
}
|
|
|
|
function createJsxFactoryExpression(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, parent: JsxOpeningLikeElement | JsxOpeningFragment): Expression {
|
|
return jsxFactoryEntity ?
|
|
createJsxFactoryExpressionFromEntityName(jsxFactoryEntity, parent) :
|
|
createPropertyAccess(
|
|
createReactNamespace(reactNamespace, parent),
|
|
"createElement"
|
|
);
|
|
}
|
|
|
|
export function createExpressionForJsxElement(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, tagName: Expression, props: Expression, children: readonly Expression[], parentElement: JsxOpeningLikeElement, location: TextRange): LeftHandSideExpression {
|
|
const argumentsList = [tagName];
|
|
if (props) {
|
|
argumentsList.push(props);
|
|
}
|
|
|
|
if (children && children.length > 0) {
|
|
if (!props) {
|
|
argumentsList.push(createNull());
|
|
}
|
|
|
|
if (children.length > 1) {
|
|
for (const child of children) {
|
|
startOnNewLine(child);
|
|
argumentsList.push(child);
|
|
}
|
|
}
|
|
else {
|
|
argumentsList.push(children[0]);
|
|
}
|
|
}
|
|
|
|
return setTextRange(
|
|
createCall(
|
|
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
|
|
/*typeArguments*/ undefined,
|
|
argumentsList
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
export function createExpressionForJsxFragment(jsxFactoryEntity: EntityName | undefined, reactNamespace: string, children: readonly Expression[], parentElement: JsxOpeningFragment, location: TextRange): LeftHandSideExpression {
|
|
const tagName = createPropertyAccess(
|
|
createReactNamespace(reactNamespace, parentElement),
|
|
"Fragment"
|
|
);
|
|
|
|
const argumentsList = [<Expression>tagName];
|
|
argumentsList.push(createNull());
|
|
|
|
if (children && children.length > 0) {
|
|
if (children.length > 1) {
|
|
for (const child of children) {
|
|
startOnNewLine(child);
|
|
argumentsList.push(child);
|
|
}
|
|
}
|
|
else {
|
|
argumentsList.push(children[0]);
|
|
}
|
|
}
|
|
|
|
return setTextRange(
|
|
createCall(
|
|
createJsxFactoryExpression(jsxFactoryEntity, reactNamespace, parentElement),
|
|
/*typeArguments*/ undefined,
|
|
argumentsList
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
// Helpers
|
|
|
|
/**
|
|
* Gets an identifier for the name of an *unscoped* emit helper.
|
|
*/
|
|
export function getUnscopedHelperName(name: string) {
|
|
return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode);
|
|
}
|
|
|
|
export const valuesHelper: UnscopedEmitHelper = {
|
|
name: "typescript:values",
|
|
importName: "__values",
|
|
scoped: false,
|
|
text: `
|
|
var __values = (this && this.__values) || function(o) {
|
|
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
if (m) return m.call(o);
|
|
if (o && typeof o.length === "number") return {
|
|
next: function () {
|
|
if (o && i >= o.length) o = void 0;
|
|
return { value: o && o[i++], done: !o };
|
|
}
|
|
};
|
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
};`
|
|
};
|
|
|
|
export function createValuesHelper(context: TransformationContext, expression: Expression, location?: TextRange) {
|
|
context.requestEmitHelper(valuesHelper);
|
|
return setTextRange(
|
|
createCall(
|
|
getUnscopedHelperName("__values"),
|
|
/*typeArguments*/ undefined,
|
|
[expression]
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
export const readHelper: UnscopedEmitHelper = {
|
|
name: "typescript:read",
|
|
importName: "__read",
|
|
scoped: false,
|
|
text: `
|
|
var __read = (this && this.__read) || function (o, n) {
|
|
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
if (!m) return o;
|
|
var i = m.call(o), r, ar = [], e;
|
|
try {
|
|
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
}
|
|
catch (error) { e = { error: error }; }
|
|
finally {
|
|
try {
|
|
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
}
|
|
finally { if (e) throw e.error; }
|
|
}
|
|
return ar;
|
|
};`
|
|
};
|
|
|
|
export function createReadHelper(context: TransformationContext, iteratorRecord: Expression, count: number | undefined, location?: TextRange) {
|
|
context.requestEmitHelper(readHelper);
|
|
return setTextRange(
|
|
createCall(
|
|
getUnscopedHelperName("__read"),
|
|
/*typeArguments*/ undefined,
|
|
count !== undefined
|
|
? [iteratorRecord, createLiteral(count)]
|
|
: [iteratorRecord]
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
export const spreadHelper: UnscopedEmitHelper = {
|
|
name: "typescript:spread",
|
|
importName: "__spread",
|
|
scoped: false,
|
|
dependencies: [readHelper],
|
|
text: `
|
|
var __spread = (this && this.__spread) || function () {
|
|
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
|
|
return ar;
|
|
};`
|
|
};
|
|
|
|
export function createSpreadHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) {
|
|
context.requestEmitHelper(spreadHelper);
|
|
return setTextRange(
|
|
createCall(
|
|
getUnscopedHelperName("__spread"),
|
|
/*typeArguments*/ undefined,
|
|
argumentList
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
export const spreadArraysHelper: UnscopedEmitHelper = {
|
|
name: "typescript:spreadArrays",
|
|
importName: "__spreadArrays",
|
|
scoped: false,
|
|
text: `
|
|
var __spreadArrays = (this && this.__spreadArrays) || function () {
|
|
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
|
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
|
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
|
r[k] = a[j];
|
|
return r;
|
|
};`
|
|
};
|
|
|
|
export function createSpreadArraysHelper(context: TransformationContext, argumentList: readonly Expression[], location?: TextRange) {
|
|
context.requestEmitHelper(spreadArraysHelper);
|
|
return setTextRange(
|
|
createCall(
|
|
getUnscopedHelperName("__spreadArrays"),
|
|
/*typeArguments*/ undefined,
|
|
argumentList
|
|
),
|
|
location
|
|
);
|
|
}
|
|
|
|
// Utilities
|
|
|
|
export function createForOfBindingStatement(node: ForInitializer, boundValue: Expression): Statement {
|
|
if (isVariableDeclarationList(node)) {
|
|
const firstDeclaration = first(node.declarations);
|
|
const updatedDeclaration = updateVariableDeclaration(
|
|
firstDeclaration,
|
|
firstDeclaration.name,
|
|
/*typeNode*/ undefined,
|
|
boundValue
|
|
);
|
|
return setTextRange(
|
|
createVariableStatement(
|
|
/*modifiers*/ undefined,
|
|
updateVariableDeclarationList(node, [updatedDeclaration])
|
|
),
|
|
/*location*/ node
|
|
);
|
|
}
|
|
else {
|
|
const updatedExpression = setTextRange(createAssignment(node, boundValue), /*location*/ node);
|
|
return setTextRange(createStatement(updatedExpression), /*location*/ node);
|
|
}
|
|
}
|
|
|
|
export function insertLeadingStatement(dest: Statement, source: Statement) {
|
|
if (isBlock(dest)) {
|
|
return updateBlock(dest, setTextRange(createNodeArray([source, ...dest.statements]), dest.statements));
|
|
}
|
|
else {
|
|
return createBlock(createNodeArray([dest, source]), /*multiLine*/ true);
|
|
}
|
|
}
|
|
|
|
export function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement | undefined, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement {
|
|
if (!outermostLabeledStatement) {
|
|
return node;
|
|
}
|
|
const updated = updateLabel(
|
|
outermostLabeledStatement,
|
|
outermostLabeledStatement.label,
|
|
outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement
|
|
? restoreEnclosingLabel(node, <LabeledStatement>outermostLabeledStatement.statement)
|
|
: node
|
|
);
|
|
if (afterRestoreLabelCallback) {
|
|
afterRestoreLabelCallback(outermostLabeledStatement);
|
|
}
|
|
return updated;
|
|
}
|
|
|
|
export interface CallBinding {
|
|
target: LeftHandSideExpression;
|
|
thisArg: Expression;
|
|
}
|
|
|
|
function shouldBeCapturedInTempVariable(node: Expression, cacheIdentifiers: boolean): boolean {
|
|
const target = skipParentheses(node);
|
|
switch (target.kind) {
|
|
case SyntaxKind.Identifier:
|
|
return cacheIdentifiers;
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.BigIntLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
return false;
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
const elements = (<ArrayLiteralExpression>target).elements;
|
|
if (elements.length === 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return (<ObjectLiteralExpression>target).properties.length > 0;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
export function createCallBinding(expression: Expression, recordTempVariable: (temp: Identifier) => void, languageVersion?: ScriptTarget, cacheIdentifiers = false): CallBinding {
|
|
const callee = skipOuterExpressions(expression, OuterExpressionKinds.All);
|
|
let thisArg: Expression;
|
|
let target: LeftHandSideExpression;
|
|
if (isSuperProperty(callee)) {
|
|
thisArg = createThis();
|
|
target = callee;
|
|
}
|
|
else if (callee.kind === SyntaxKind.SuperKeyword) {
|
|
thisArg = createThis();
|
|
target = languageVersion! < ScriptTarget.ES2015
|
|
? setTextRange(createIdentifier("_super"), callee)
|
|
: <PrimaryExpression>callee;
|
|
}
|
|
else if (getEmitFlags(callee) & EmitFlags.HelperName) {
|
|
thisArg = createVoidZero();
|
|
target = parenthesizeForAccess(callee);
|
|
}
|
|
else {
|
|
switch (callee.kind) {
|
|
case SyntaxKind.PropertyAccessExpression: {
|
|
if (shouldBeCapturedInTempVariable((<PropertyAccessExpression>callee).expression, cacheIdentifiers)) {
|
|
// for `a.b()` target is `(_a = a).b` and thisArg is `_a`
|
|
thisArg = createTempVariable(recordTempVariable);
|
|
target = createPropertyAccess(
|
|
setTextRange(
|
|
createAssignment(
|
|
thisArg,
|
|
(<PropertyAccessExpression>callee).expression
|
|
),
|
|
(<PropertyAccessExpression>callee).expression
|
|
),
|
|
(<PropertyAccessExpression>callee).name
|
|
);
|
|
setTextRange(target, callee);
|
|
}
|
|
else {
|
|
thisArg = (<PropertyAccessExpression>callee).expression;
|
|
target = <PropertyAccessExpression>callee;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SyntaxKind.ElementAccessExpression: {
|
|
if (shouldBeCapturedInTempVariable((<ElementAccessExpression>callee).expression, cacheIdentifiers)) {
|
|
// for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a`
|
|
thisArg = createTempVariable(recordTempVariable);
|
|
target = createElementAccess(
|
|
setTextRange(
|
|
createAssignment(
|
|
thisArg,
|
|
(<ElementAccessExpression>callee).expression
|
|
),
|
|
(<ElementAccessExpression>callee).expression
|
|
),
|
|
(<ElementAccessExpression>callee).argumentExpression
|
|
);
|
|
setTextRange(target, callee);
|
|
}
|
|
else {
|
|
thisArg = (<ElementAccessExpression>callee).expression;
|
|
target = <ElementAccessExpression>callee;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// for `a()` target is `a` and thisArg is `void 0`
|
|
thisArg = createVoidZero();
|
|
target = parenthesizeForAccess(expression);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return { target, thisArg };
|
|
}
|
|
|
|
export function inlineExpressions(expressions: readonly Expression[]) {
|
|
// Avoid deeply nested comma expressions as traversing them during emit can result in "Maximum call
|
|
// stack size exceeded" errors.
|
|
return expressions.length > 10
|
|
? createCommaList(expressions)
|
|
: reduceLeft(expressions, createComma)!;
|
|
}
|
|
|
|
export function createExpressionFromEntityName(node: EntityName | Expression): Expression {
|
|
if (isQualifiedName(node)) {
|
|
const left = createExpressionFromEntityName(node.left);
|
|
const right = getMutableClone(node.right);
|
|
return setTextRange(createPropertyAccess(left, right), node);
|
|
}
|
|
else {
|
|
return getMutableClone(node);
|
|
}
|
|
}
|
|
|
|
export function createExpressionForPropertyName(memberName: Exclude<PropertyName, PrivateIdentifier>): Expression {
|
|
if (isIdentifier(memberName)) {
|
|
return createLiteral(memberName);
|
|
}
|
|
else if (isComputedPropertyName(memberName)) {
|
|
return getMutableClone(memberName.expression);
|
|
}
|
|
else {
|
|
return getMutableClone(memberName);
|
|
}
|
|
}
|
|
|
|
export function createExpressionForObjectLiteralElementLike(node: ObjectLiteralExpression, property: ObjectLiteralElementLike, receiver: Expression): Expression | undefined {
|
|
if (property.name && isPrivateIdentifier(property.name)) {
|
|
Debug.failBadSyntaxKind(property.name, "Private identifiers are not allowed in object literals.");
|
|
}
|
|
switch (property.kind) {
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
return createExpressionForAccessorDeclaration(node.properties, property as typeof property & { name: Exclude<PropertyName, PrivateIdentifier>; }, receiver, !!node.multiLine);
|
|
case SyntaxKind.PropertyAssignment:
|
|
return createExpressionForPropertyAssignment(property, receiver);
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
return createExpressionForShorthandPropertyAssignment(property, receiver);
|
|
case SyntaxKind.MethodDeclaration:
|
|
return createExpressionForMethodDeclaration(property, receiver);
|
|
}
|
|
}
|
|
|
|
function createExpressionForAccessorDeclaration(properties: NodeArray<Declaration>, property: AccessorDeclaration & { name: Exclude<PropertyName, PrivateIdentifier>; }, receiver: Expression, multiLine: boolean) {
|
|
const { firstAccessor, getAccessor, setAccessor } = getAllAccessorDeclarations(properties, property);
|
|
if (property === firstAccessor) {
|
|
const properties: ObjectLiteralElementLike[] = [];
|
|
if (getAccessor) {
|
|
const getterFunction = createFunctionExpression(
|
|
getAccessor.modifiers,
|
|
/*asteriskToken*/ undefined,
|
|
/*name*/ undefined,
|
|
/*typeParameters*/ undefined,
|
|
getAccessor.parameters,
|
|
/*type*/ undefined,
|
|
getAccessor.body! // TODO: GH#18217
|
|
);
|
|
setTextRange(getterFunction, getAccessor);
|
|
setOriginalNode(getterFunction, getAccessor);
|
|
const getter = createPropertyAssignment("get", getterFunction);
|
|
properties.push(getter);
|
|
}
|
|
|
|
if (setAccessor) {
|
|
const setterFunction = createFunctionExpression(
|
|
setAccessor.modifiers,
|
|
/*asteriskToken*/ undefined,
|
|
/*name*/ undefined,
|
|
/*typeParameters*/ undefined,
|
|
setAccessor.parameters,
|
|
/*type*/ undefined,
|
|
setAccessor.body! // TODO: GH#18217
|
|
);
|
|
setTextRange(setterFunction, setAccessor);
|
|
setOriginalNode(setterFunction, setAccessor);
|
|
const setter = createPropertyAssignment("set", setterFunction);
|
|
properties.push(setter);
|
|
}
|
|
|
|
properties.push(createPropertyAssignment("enumerable", getAccessor || setAccessor ? createFalse() : createTrue()));
|
|
properties.push(createPropertyAssignment("configurable", createTrue()));
|
|
|
|
const expression = setTextRange(
|
|
createCall(
|
|
createPropertyAccess(createIdentifier("Object"), "defineProperty"),
|
|
/*typeArguments*/ undefined,
|
|
[
|
|
receiver,
|
|
createExpressionForPropertyName(property.name),
|
|
createObjectLiteral(properties, multiLine)
|
|
]
|
|
),
|
|
/*location*/ firstAccessor
|
|
);
|
|
|
|
return aggregateTransformFlags(expression);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function createExpressionForPropertyAssignment(property: PropertyAssignment, receiver: Expression) {
|
|
return aggregateTransformFlags(
|
|
setOriginalNode(
|
|
setTextRange(
|
|
createAssignment(
|
|
createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name),
|
|
property.initializer
|
|
),
|
|
property
|
|
),
|
|
property
|
|
)
|
|
);
|
|
}
|
|
|
|
function createExpressionForShorthandPropertyAssignment(property: ShorthandPropertyAssignment, receiver: Expression) {
|
|
return aggregateTransformFlags(
|
|
setOriginalNode(
|
|
setTextRange(
|
|
createAssignment(
|
|
createMemberAccessForPropertyName(receiver, property.name, /*location*/ property.name),
|
|
getSynthesizedClone(property.name)
|
|
),
|
|
/*location*/ property
|
|
),
|
|
/*original*/ property
|
|
)
|
|
);
|
|
}
|
|
|
|
function createExpressionForMethodDeclaration(method: MethodDeclaration, receiver: Expression) {
|
|
return aggregateTransformFlags(
|
|
setOriginalNode(
|
|
setTextRange(
|
|
createAssignment(
|
|
createMemberAccessForPropertyName(receiver, method.name, /*location*/ method.name),
|
|
setOriginalNode(
|
|
setTextRange(
|
|
createFunctionExpression(
|
|
method.modifiers,
|
|
method.asteriskToken,
|
|
/*name*/ undefined,
|
|
/*typeParameters*/ undefined,
|
|
method.parameters,
|
|
/*type*/ undefined,
|
|
method.body! // TODO: GH#18217
|
|
),
|
|
/*location*/ method
|
|
),
|
|
/*original*/ method
|
|
)
|
|
),
|
|
/*location*/ method
|
|
),
|
|
/*original*/ method
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets the internal name of a declaration. This is primarily used for declarations that can be
|
|
* referred to by name in the body of an ES5 class function body. An internal name will *never*
|
|
* be prefixed with an module or namespace export modifier like "exports." when emitted as an
|
|
* expression. An internal name will also *never* be renamed due to a collision with a block
|
|
* scoped variable.
|
|
*
|
|
* @param node The declaration.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getInternalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) {
|
|
return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName | EmitFlags.InternalName);
|
|
}
|
|
|
|
/**
|
|
* Gets whether an identifier should only be referred to by its internal name.
|
|
*/
|
|
export function isInternalName(node: Identifier) {
|
|
return (getEmitFlags(node) & EmitFlags.InternalName) !== 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the local name of a declaration. This is primarily used for declarations that can be
|
|
* referred to by name in the declaration's immediate scope (classes, enums, namespaces). A
|
|
* local name will *never* be prefixed with an module or namespace export modifier like
|
|
* "exports." when emitted as an expression.
|
|
*
|
|
* @param node The declaration.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) {
|
|
return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName);
|
|
}
|
|
|
|
/**
|
|
* Gets whether an identifier should only be referred to by its local name.
|
|
*/
|
|
export function isLocalName(node: Identifier) {
|
|
return (getEmitFlags(node) & EmitFlags.LocalName) !== 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the export name of a declaration. This is primarily used for declarations that can be
|
|
* referred to by name in the declaration's immediate scope (classes, enums, namespaces). An
|
|
* export name will *always* be prefixed with an module or namespace export modifier like
|
|
* `"exports."` when emitted as an expression if the name points to an exported symbol.
|
|
*
|
|
* @param node The declaration.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getExportName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier {
|
|
return getName(node, allowComments, allowSourceMaps, EmitFlags.ExportName);
|
|
}
|
|
|
|
/**
|
|
* Gets whether an identifier should only be referred to by its export representation if the
|
|
* name points to an exported symbol.
|
|
*/
|
|
export function isExportName(node: Identifier) {
|
|
return (getEmitFlags(node) & EmitFlags.ExportName) !== 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the name of a declaration for use in declarations.
|
|
*
|
|
* @param node The declaration.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getDeclarationName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) {
|
|
return getName(node, allowComments, allowSourceMaps);
|
|
}
|
|
|
|
function getName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) {
|
|
const nodeName = getNameOfDeclaration(node);
|
|
if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) {
|
|
const name = getMutableClone(nodeName);
|
|
emitFlags |= getEmitFlags(nodeName);
|
|
if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap;
|
|
if (!allowComments) emitFlags |= EmitFlags.NoComments;
|
|
if (emitFlags) setEmitFlags(name, emitFlags);
|
|
return name;
|
|
}
|
|
return getGeneratedNameForNode(node);
|
|
}
|
|
|
|
/**
|
|
* Gets the exported name of a declaration for use in expressions.
|
|
*
|
|
* An exported name will *always* be prefixed with an module or namespace export modifier like
|
|
* "exports." if the name points to an exported symbol.
|
|
*
|
|
* @param ns The namespace identifier.
|
|
* @param node The declaration.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getExternalModuleOrNamespaceExportName(ns: Identifier | undefined, node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier | PropertyAccessExpression {
|
|
if (ns && hasModifier(node, ModifierFlags.Export)) {
|
|
return getNamespaceMemberName(ns, getName(node), allowComments, allowSourceMaps);
|
|
}
|
|
return getExportName(node, allowComments, allowSourceMaps);
|
|
}
|
|
|
|
/**
|
|
* Gets a namespace-qualified name for use in expressions.
|
|
*
|
|
* @param ns The namespace identifier.
|
|
* @param name The name.
|
|
* @param allowComments A value indicating whether comments may be emitted for the name.
|
|
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
|
|
*/
|
|
export function getNamespaceMemberName(ns: Identifier, name: Identifier, allowComments?: boolean, allowSourceMaps?: boolean): PropertyAccessExpression {
|
|
const qualifiedName = createPropertyAccess(ns, nodeIsSynthesized(name) ? name : getSynthesizedClone(name));
|
|
setTextRange(qualifiedName, name);
|
|
let emitFlags: EmitFlags = 0;
|
|
if (!allowSourceMaps) emitFlags |= EmitFlags.NoSourceMap;
|
|
if (!allowComments) emitFlags |= EmitFlags.NoComments;
|
|
if (emitFlags) setEmitFlags(qualifiedName, emitFlags);
|
|
return qualifiedName;
|
|
}
|
|
|
|
export function convertToFunctionBody(node: ConciseBody, multiLine?: boolean): Block {
|
|
return isBlock(node) ? node : setTextRange(createBlock([setTextRange(createReturn(node), node)], multiLine), node);
|
|
}
|
|
|
|
export function convertFunctionDeclarationToExpression(node: FunctionDeclaration) {
|
|
if (!node.body) return Debug.fail();
|
|
const updated = createFunctionExpression(
|
|
node.modifiers,
|
|
node.asteriskToken,
|
|
node.name,
|
|
node.typeParameters,
|
|
node.parameters,
|
|
node.type,
|
|
node.body
|
|
);
|
|
setOriginalNode(updated, node);
|
|
setTextRange(updated, node);
|
|
if (getStartsOnNewLine(node)) {
|
|
setStartsOnNewLine(updated, /*newLine*/ true);
|
|
}
|
|
aggregateTransformFlags(updated);
|
|
return updated;
|
|
}
|
|
|
|
function isUseStrictPrologue(node: ExpressionStatement): boolean {
|
|
return isStringLiteral(node.expression) && node.expression.text === "use strict";
|
|
}
|
|
|
|
/**
|
|
* Add any necessary prologue-directives into target statement-array.
|
|
* The function needs to be called during each transformation step.
|
|
* This function needs to be called whenever we transform the statement
|
|
* list of a source file, namespace, or function-like body.
|
|
*
|
|
* @param target: result statements array
|
|
* @param source: origin statements array
|
|
* @param ensureUseStrict: boolean determining whether the function need to add prologue-directives
|
|
* @param visitor: Optional callback used to visit any custom prologue directives.
|
|
*/
|
|
export function addPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult<Node>): number {
|
|
const offset = addStandardPrologue(target, source, ensureUseStrict);
|
|
return addCustomPrologue(target, source, offset, visitor);
|
|
}
|
|
|
|
/**
|
|
* Add just the standard (string-expression) prologue-directives into target statement-array.
|
|
* The function needs to be called during each transformation step.
|
|
* This function needs to be called whenever we transform the statement
|
|
* list of a source file, namespace, or function-like body.
|
|
*/
|
|
export function addStandardPrologue(target: Statement[], source: readonly Statement[], ensureUseStrict?: boolean): number {
|
|
Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array");
|
|
let foundUseStrict = false;
|
|
let statementOffset = 0;
|
|
const numStatements = source.length;
|
|
while (statementOffset < numStatements) {
|
|
const statement = source[statementOffset];
|
|
if (isPrologueDirective(statement)) {
|
|
if (isUseStrictPrologue(statement)) {
|
|
foundUseStrict = true;
|
|
}
|
|
target.push(statement);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
statementOffset++;
|
|
}
|
|
if (ensureUseStrict && !foundUseStrict) {
|
|
target.push(startOnNewLine(createStatement(createLiteral("use strict"))));
|
|
}
|
|
return statementOffset;
|
|
}
|
|
|
|
/**
|
|
* Add just the custom prologue-directives into target statement-array.
|
|
* The function needs to be called during each transformation step.
|
|
* This function needs to be called whenever we transform the statement
|
|
* list of a source file, namespace, or function-like body.
|
|
*/
|
|
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number, visitor?: (node: Node) => VisitResult<Node>): number;
|
|
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>): number | undefined;
|
|
export function addCustomPrologue(target: Statement[], source: readonly Statement[], statementOffset: number | undefined, visitor?: (node: Node) => VisitResult<Node>): number | undefined {
|
|
const numStatements = source.length;
|
|
while (statementOffset !== undefined && statementOffset < numStatements) {
|
|
const statement = source[statementOffset];
|
|
if (getEmitFlags(statement) & EmitFlags.CustomPrologue) {
|
|
append(target, visitor ? visitNode(statement, visitor, isStatement) : statement);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
statementOffset++;
|
|
}
|
|
return statementOffset;
|
|
}
|
|
|
|
export function findUseStrictPrologue(statements: readonly Statement[]): Statement | undefined {
|
|
for (const statement of statements) {
|
|
if (isPrologueDirective(statement)) {
|
|
if (isUseStrictPrologue(statement)) {
|
|
return statement;
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function startsWithUseStrict(statements: readonly Statement[]) {
|
|
const firstStatement = firstOrUndefined(statements);
|
|
return firstStatement !== undefined
|
|
&& isPrologueDirective(firstStatement)
|
|
&& isUseStrictPrologue(firstStatement);
|
|
}
|
|
|
|
/**
|
|
* Ensures "use strict" directive is added
|
|
*
|
|
* @param statements An array of statements
|
|
*/
|
|
export function ensureUseStrict(statements: NodeArray<Statement>): NodeArray<Statement> {
|
|
const foundUseStrict = findUseStrictPrologue(statements);
|
|
|
|
if (!foundUseStrict) {
|
|
return setTextRange(
|
|
createNodeArray<Statement>([
|
|
startOnNewLine(createStatement(createLiteral("use strict"))),
|
|
...statements
|
|
]),
|
|
statements
|
|
);
|
|
}
|
|
|
|
return statements;
|
|
}
|
|
|
|
/**
|
|
* Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended
|
|
* order of operations.
|
|
*
|
|
* @param binaryOperator The operator for the BinaryExpression.
|
|
* @param operand The operand for the BinaryExpression.
|
|
* @param isLeftSideOfBinary A value indicating whether the operand is the left side of the
|
|
* BinaryExpression.
|
|
*/
|
|
export function parenthesizeBinaryOperand(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand?: Expression) {
|
|
const skipped = skipPartiallyEmittedExpressions(operand);
|
|
|
|
// If the resulting expression is already parenthesized, we do not need to do any further processing.
|
|
if (skipped.kind === SyntaxKind.ParenthesizedExpression) {
|
|
return operand;
|
|
}
|
|
|
|
return binaryOperandNeedsParentheses(binaryOperator, operand, isLeftSideOfBinary, leftOperand)
|
|
? createParen(operand)
|
|
: operand;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the operand to a BinaryExpression needs to be parenthesized.
|
|
*
|
|
* @param binaryOperator The operator for the BinaryExpression.
|
|
* @param operand The operand for the BinaryExpression.
|
|
* @param isLeftSideOfBinary A value indicating whether the operand is the left side of the
|
|
* BinaryExpression.
|
|
*/
|
|
function binaryOperandNeedsParentheses(binaryOperator: SyntaxKind, operand: Expression, isLeftSideOfBinary: boolean, leftOperand: Expression | undefined) {
|
|
// If the operand has lower precedence, then it needs to be parenthesized to preserve the
|
|
// intent of the expression. For example, if the operand is `a + b` and the operator is
|
|
// `*`, then we need to parenthesize the operand to preserve the intended order of
|
|
// operations: `(a + b) * x`.
|
|
//
|
|
// If the operand has higher precedence, then it does not need to be parenthesized. For
|
|
// example, if the operand is `a * b` and the operator is `+`, then we do not need to
|
|
// parenthesize to preserve the intended order of operations: `a * b + x`.
|
|
//
|
|
// If the operand has the same precedence, then we need to check the associativity of
|
|
// the operator based on whether this is the left or right operand of the expression.
|
|
//
|
|
// For example, if `a / d` is on the right of operator `*`, we need to parenthesize
|
|
// to preserve the intended order of operations: `x * (a / d)`
|
|
//
|
|
// If `a ** d` is on the left of operator `**`, we need to parenthesize to preserve
|
|
// the intended order of operations: `(a ** b) ** c`
|
|
const binaryOperatorPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, binaryOperator);
|
|
const binaryOperatorAssociativity = getOperatorAssociativity(SyntaxKind.BinaryExpression, binaryOperator);
|
|
const emittedOperand = skipPartiallyEmittedExpressions(operand);
|
|
if (!isLeftSideOfBinary && operand.kind === SyntaxKind.ArrowFunction && binaryOperatorPrecedence > 3) {
|
|
// We need to parenthesize arrow functions on the right side to avoid it being
|
|
// parsed as parenthesized expression: `a && (() => {})`
|
|
return true;
|
|
}
|
|
const operandPrecedence = getExpressionPrecedence(emittedOperand);
|
|
switch (compareValues(operandPrecedence, binaryOperatorPrecedence)) {
|
|
case Comparison.LessThan:
|
|
// If the operand is the right side of a right-associative binary operation
|
|
// and is a yield expression, then we do not need parentheses.
|
|
if (!isLeftSideOfBinary
|
|
&& binaryOperatorAssociativity === Associativity.Right
|
|
&& operand.kind === SyntaxKind.YieldExpression) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
case Comparison.GreaterThan:
|
|
return false;
|
|
|
|
case Comparison.EqualTo:
|
|
if (isLeftSideOfBinary) {
|
|
// No need to parenthesize the left operand when the binary operator is
|
|
// left associative:
|
|
// (a*b)/x -> a*b/x
|
|
// (a**b)/x -> a**b/x
|
|
//
|
|
// Parentheses are needed for the left operand when the binary operator is
|
|
// right associative:
|
|
// (a/b)**x -> (a/b)**x
|
|
// (a**b)**x -> (a**b)**x
|
|
return binaryOperatorAssociativity === Associativity.Right;
|
|
}
|
|
else {
|
|
if (isBinaryExpression(emittedOperand)
|
|
&& emittedOperand.operatorToken.kind === binaryOperator) {
|
|
// No need to parenthesize the right operand when the binary operator and
|
|
// operand are the same and one of the following:
|
|
// x*(a*b) => x*a*b
|
|
// x|(a|b) => x|a|b
|
|
// x&(a&b) => x&a&b
|
|
// x^(a^b) => x^a^b
|
|
if (operatorHasAssociativeProperty(binaryOperator)) {
|
|
return false;
|
|
}
|
|
|
|
// No need to parenthesize the right operand when the binary operator
|
|
// is plus (+) if both the left and right operands consist solely of either
|
|
// literals of the same kind or binary plus (+) expressions for literals of
|
|
// the same kind (recursively).
|
|
// "a"+(1+2) => "a"+(1+2)
|
|
// "a"+("b"+"c") => "a"+"b"+"c"
|
|
if (binaryOperator === SyntaxKind.PlusToken) {
|
|
const leftKind = leftOperand ? getLiteralKindOfBinaryPlusOperand(leftOperand) : SyntaxKind.Unknown;
|
|
if (isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand(emittedOperand)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No need to parenthesize the right operand when the operand is right
|
|
// associative:
|
|
// x/(a**b) -> x/a**b
|
|
// x**(a**b) -> x**a**b
|
|
//
|
|
// Parentheses are needed for the right operand when the operand is left
|
|
// associative:
|
|
// x/(a*b) -> x/(a*b)
|
|
// x**(a/b) -> x**(a/b)
|
|
const operandAssociativity = getExpressionAssociativity(emittedOperand);
|
|
return operandAssociativity === Associativity.Left;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a binary operator is mathematically associative.
|
|
*
|
|
* @param binaryOperator The binary operator.
|
|
*/
|
|
function operatorHasAssociativeProperty(binaryOperator: SyntaxKind) {
|
|
// The following operators are associative in JavaScript:
|
|
// (a*b)*c -> a*(b*c) -> a*b*c
|
|
// (a|b)|c -> a|(b|c) -> a|b|c
|
|
// (a&b)&c -> a&(b&c) -> a&b&c
|
|
// (a^b)^c -> a^(b^c) -> a^b^c
|
|
//
|
|
// While addition is associative in mathematics, JavaScript's `+` is not
|
|
// guaranteed to be associative as it is overloaded with string concatenation.
|
|
return binaryOperator === SyntaxKind.AsteriskToken
|
|
|| binaryOperator === SyntaxKind.BarToken
|
|
|| binaryOperator === SyntaxKind.AmpersandToken
|
|
|| binaryOperator === SyntaxKind.CaretToken;
|
|
}
|
|
|
|
interface BinaryPlusExpression extends BinaryExpression {
|
|
cachedLiteralKind: SyntaxKind;
|
|
}
|
|
|
|
/**
|
|
* This function determines whether an expression consists of a homogeneous set of
|
|
* literal expressions or binary plus expressions that all share the same literal kind.
|
|
* It is used to determine whether the right-hand operand of a binary plus expression can be
|
|
* emitted without parentheses.
|
|
*/
|
|
function getLiteralKindOfBinaryPlusOperand(node: Expression): SyntaxKind {
|
|
node = skipPartiallyEmittedExpressions(node);
|
|
|
|
if (isLiteralKind(node.kind)) {
|
|
return node.kind;
|
|
}
|
|
|
|
if (node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.PlusToken) {
|
|
if ((<BinaryPlusExpression>node).cachedLiteralKind !== undefined) {
|
|
return (<BinaryPlusExpression>node).cachedLiteralKind;
|
|
}
|
|
|
|
const leftKind = getLiteralKindOfBinaryPlusOperand((<BinaryExpression>node).left);
|
|
const literalKind = isLiteralKind(leftKind) && leftKind === getLiteralKindOfBinaryPlusOperand((<BinaryExpression>node).right) ? leftKind :
|
|
SyntaxKind.Unknown;
|
|
|
|
(<BinaryPlusExpression>node).cachedLiteralKind = literalKind;
|
|
return literalKind;
|
|
}
|
|
|
|
return SyntaxKind.Unknown;
|
|
}
|
|
|
|
export function parenthesizeForConditionalHead(condition: Expression) {
|
|
const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken);
|
|
const emittedCondition = skipPartiallyEmittedExpressions(condition);
|
|
const conditionPrecedence = getExpressionPrecedence(emittedCondition);
|
|
if (compareValues(conditionPrecedence, conditionalPrecedence) !== Comparison.GreaterThan) {
|
|
return createParen(condition);
|
|
}
|
|
return condition;
|
|
}
|
|
|
|
export function parenthesizeSubexpressionOfConditionalExpression(e: Expression): Expression {
|
|
// per ES grammar both 'whenTrue' and 'whenFalse' parts of conditional expression are assignment expressions
|
|
// so in case when comma expression is introduced as a part of previous transformations
|
|
// if should be wrapped in parens since comma operator has the lowest precedence
|
|
const emittedExpression = skipPartiallyEmittedExpressions(e);
|
|
return isCommaSequence(emittedExpression)
|
|
? createParen(e)
|
|
: e;
|
|
}
|
|
|
|
/**
|
|
* [Per the spec](https://tc39.github.io/ecma262/#prod-ExportDeclaration), `export default` accepts _AssigmentExpression_ but
|
|
* has a lookahead restriction for `function`, `async function`, and `class`.
|
|
*
|
|
* Basically, that means we need to parenthesize in the following cases:
|
|
*
|
|
* - BinaryExpression of CommaToken
|
|
* - CommaList (synthetic list of multiple comma expressions)
|
|
* - FunctionExpression
|
|
* - ClassExpression
|
|
*/
|
|
export function parenthesizeDefaultExpression(e: Expression) {
|
|
const check = skipPartiallyEmittedExpressions(e);
|
|
let needsParens = isCommaSequence(check);
|
|
if (!needsParens) {
|
|
switch (getLeftmostExpression(check, /*stopAtCallExpression*/ false).kind) {
|
|
case SyntaxKind.ClassExpression:
|
|
case SyntaxKind.FunctionExpression:
|
|
needsParens = true;
|
|
}
|
|
}
|
|
return needsParens ? createParen(e) : e;
|
|
}
|
|
|
|
/**
|
|
* Wraps an expression in parentheses if it is needed in order to use the expression
|
|
* as the expression of a NewExpression node.
|
|
*
|
|
* @param expression The Expression node.
|
|
*/
|
|
export function parenthesizeForNew(expression: Expression): LeftHandSideExpression {
|
|
const leftmostExpr = getLeftmostExpression(expression, /*stopAtCallExpressions*/ true);
|
|
switch (leftmostExpr.kind) {
|
|
case SyntaxKind.CallExpression:
|
|
return createParen(expression);
|
|
|
|
case SyntaxKind.NewExpression:
|
|
return !(leftmostExpr as NewExpression).arguments
|
|
? createParen(expression)
|
|
: <LeftHandSideExpression>expression;
|
|
}
|
|
|
|
return parenthesizeForAccess(expression);
|
|
}
|
|
|
|
/**
|
|
* Wraps an expression in parentheses if it is needed in order to use the expression for
|
|
* property or element access.
|
|
*
|
|
* @param expr The expression node.
|
|
*/
|
|
export function parenthesizeForAccess(expression: Expression): LeftHandSideExpression {
|
|
// isLeftHandSideExpression is almost the correct criterion for when it is not necessary
|
|
// to parenthesize the expression before a dot. The known exception is:
|
|
//
|
|
// NewExpression:
|
|
// new C.x -> not the same as (new C).x
|
|
//
|
|
const emittedExpression = skipPartiallyEmittedExpressions(expression);
|
|
if (isLeftHandSideExpression(emittedExpression)
|
|
&& (emittedExpression.kind !== SyntaxKind.NewExpression || (<NewExpression>emittedExpression).arguments)) {
|
|
return <LeftHandSideExpression>expression;
|
|
}
|
|
|
|
return setTextRange(createParen(expression), expression);
|
|
}
|
|
|
|
export function parenthesizePostfixOperand(operand: Expression) {
|
|
return isLeftHandSideExpression(operand)
|
|
? operand
|
|
: setTextRange(createParen(operand), operand);
|
|
}
|
|
|
|
export function parenthesizePrefixOperand(operand: Expression) {
|
|
return isUnaryExpression(operand)
|
|
? operand
|
|
: setTextRange(createParen(operand), operand);
|
|
}
|
|
|
|
export function parenthesizeListElements(elements: NodeArray<Expression>) {
|
|
let result: Expression[] | undefined;
|
|
for (let i = 0; i < elements.length; i++) {
|
|
const element = parenthesizeExpressionForList(elements[i]);
|
|
if (result !== undefined || element !== elements[i]) {
|
|
if (result === undefined) {
|
|
result = elements.slice(0, i);
|
|
}
|
|
|
|
result.push(element);
|
|
}
|
|
}
|
|
|
|
if (result !== undefined) {
|
|
return setTextRange(createNodeArray(result, elements.hasTrailingComma), elements);
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
export function parenthesizeExpressionForList(expression: Expression) {
|
|
const emittedExpression = skipPartiallyEmittedExpressions(expression);
|
|
const expressionPrecedence = getExpressionPrecedence(emittedExpression);
|
|
const commaPrecedence = getOperatorPrecedence(SyntaxKind.BinaryExpression, SyntaxKind.CommaToken);
|
|
return expressionPrecedence > commaPrecedence
|
|
? expression
|
|
: setTextRange(createParen(expression), expression);
|
|
}
|
|
|
|
export function parenthesizeExpressionForExpressionStatement(expression: Expression) {
|
|
const emittedExpression = skipPartiallyEmittedExpressions(expression);
|
|
if (isCallExpression(emittedExpression)) {
|
|
const callee = emittedExpression.expression;
|
|
const kind = skipPartiallyEmittedExpressions(callee).kind;
|
|
if (kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction) {
|
|
const mutableCall = getMutableClone(emittedExpression);
|
|
mutableCall.expression = setTextRange(createParen(callee), callee);
|
|
return recreateOuterExpressions(expression, mutableCall, OuterExpressionKinds.PartiallyEmittedExpressions);
|
|
}
|
|
}
|
|
|
|
const leftmostExpressionKind = getLeftmostExpression(emittedExpression, /*stopAtCallExpressions*/ false).kind;
|
|
if (leftmostExpressionKind === SyntaxKind.ObjectLiteralExpression || leftmostExpressionKind === SyntaxKind.FunctionExpression) {
|
|
return setTextRange(createParen(expression), expression);
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
export function parenthesizeConditionalTypeMember(member: TypeNode) {
|
|
return member.kind === SyntaxKind.ConditionalType ? createParenthesizedType(member) : member;
|
|
}
|
|
|
|
export function parenthesizeElementTypeMember(member: TypeNode) {
|
|
switch (member.kind) {
|
|
case SyntaxKind.UnionType:
|
|
case SyntaxKind.IntersectionType:
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
return createParenthesizedType(member);
|
|
}
|
|
return parenthesizeConditionalTypeMember(member);
|
|
}
|
|
|
|
export function parenthesizeArrayTypeMember(member: TypeNode) {
|
|
switch (member.kind) {
|
|
case SyntaxKind.TypeQuery:
|
|
case SyntaxKind.TypeOperator:
|
|
case SyntaxKind.InferType:
|
|
return createParenthesizedType(member);
|
|
}
|
|
return parenthesizeElementTypeMember(member);
|
|
}
|
|
|
|
export function parenthesizeElementTypeMembers(members: readonly TypeNode[]) {
|
|
return createNodeArray(sameMap(members, parenthesizeElementTypeMember));
|
|
}
|
|
|
|
export function parenthesizeTypeParameters(typeParameters: readonly TypeNode[] | undefined) {
|
|
if (some(typeParameters)) {
|
|
const params: TypeNode[] = [];
|
|
for (let i = 0; i < typeParameters.length; ++i) {
|
|
const entry = typeParameters[i];
|
|
params.push(i === 0 && isFunctionOrConstructorTypeNode(entry) && entry.typeParameters ?
|
|
createParenthesizedType(entry) :
|
|
entry);
|
|
}
|
|
|
|
return createNodeArray(params);
|
|
}
|
|
}
|
|
|
|
export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
|
|
while (true) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
node = (<PostfixUnaryExpression>node).operand;
|
|
continue;
|
|
|
|
case SyntaxKind.BinaryExpression:
|
|
node = (<BinaryExpression>node).left;
|
|
continue;
|
|
|
|
case SyntaxKind.ConditionalExpression:
|
|
node = (<ConditionalExpression>node).condition;
|
|
continue;
|
|
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
node = (<TaggedTemplateExpression>node).tag;
|
|
continue;
|
|
|
|
case SyntaxKind.CallExpression:
|
|
if (stopAtCallExpressions) {
|
|
return node;
|
|
}
|
|
// falls through
|
|
case SyntaxKind.AsExpression:
|
|
case SyntaxKind.ElementAccessExpression:
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
case SyntaxKind.NonNullExpression:
|
|
case SyntaxKind.PartiallyEmittedExpression:
|
|
node = (<CallExpression | PropertyAccessExpression | ElementAccessExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression>node).expression;
|
|
continue;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
}
|
|
|
|
export function parenthesizeConciseBody(body: ConciseBody): ConciseBody {
|
|
if (!isBlock(body) && (isCommaSequence(body) || getLeftmostExpression(body, /*stopAtCallExpressions*/ false).kind === SyntaxKind.ObjectLiteralExpression)) {
|
|
return setTextRange(createParen(body), body);
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
export function isCommaSequence(node: Expression): node is BinaryExpression & {operatorToken: Token<SyntaxKind.CommaToken>} | CommaListExpression {
|
|
return node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.CommaToken ||
|
|
node.kind === SyntaxKind.CommaListExpression;
|
|
}
|
|
|
|
export const enum OuterExpressionKinds {
|
|
Parentheses = 1 << 0,
|
|
TypeAssertions = 1 << 1,
|
|
NonNullAssertions = 1 << 2,
|
|
PartiallyEmittedExpressions = 1 << 3,
|
|
|
|
Assertions = TypeAssertions | NonNullAssertions,
|
|
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:
|
|
return (kinds & OuterExpressionKinds.TypeAssertions) !== 0;
|
|
case SyntaxKind.NonNullExpression:
|
|
return (kinds & OuterExpressionKinds.NonNullAssertions) !== 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) {
|
|
while (isOuterExpression(node, kinds)) {
|
|
node = node.expression;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
export function skipAssertions(node: Expression): Expression;
|
|
export function skipAssertions(node: Node): Node;
|
|
export function skipAssertions(node: Node): Node {
|
|
return skipOuterExpressions(node, OuterExpressionKinds.Assertions);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether a node is a parenthesized expression that can be ignored when recreating outer expressions.
|
|
*
|
|
* A parenthesized expression can be ignored when all of the following are true:
|
|
*
|
|
* - It's `pos` and `end` are not -1
|
|
* - It does not have a custom source map range
|
|
* - It does not have a custom comment range
|
|
* - It does not have synthetic leading or trailing comments
|
|
*
|
|
* If an outermost parenthesized expression is ignored, but the containing expression requires a parentheses around
|
|
* the expression to maintain precedence, a new parenthesized expression should be created automatically when
|
|
* the containing expression is created/updated.
|
|
*/
|
|
function isIgnorableParen(node: Expression) {
|
|
return node.kind === SyntaxKind.ParenthesizedExpression
|
|
&& nodeIsSynthesized(node)
|
|
&& nodeIsSynthesized(getSourceMapRange(node))
|
|
&& nodeIsSynthesized(getCommentRange(node))
|
|
&& !some(getSyntheticLeadingComments(node))
|
|
&& !some(getSyntheticTrailingComments(node));
|
|
}
|
|
|
|
export function recreateOuterExpressions(outerExpression: Expression | undefined, innerExpression: Expression, kinds = OuterExpressionKinds.All): Expression {
|
|
if (outerExpression && isOuterExpression(outerExpression, kinds) && !isIgnorableParen(outerExpression)) {
|
|
return updateOuterExpression(
|
|
outerExpression,
|
|
recreateOuterExpressions(outerExpression.expression, innerExpression)
|
|
);
|
|
}
|
|
return innerExpression;
|
|
}
|
|
|
|
export function startOnNewLine<T extends Node>(node: T): T {
|
|
return setStartsOnNewLine(node, /*newLine*/ true);
|
|
}
|
|
|
|
export function getExternalHelpersModuleName(node: SourceFile) {
|
|
const parseNode = getOriginalNode(node, isSourceFile);
|
|
const emitNode = parseNode && parseNode.emitNode;
|
|
return emitNode && emitNode.externalHelpersModuleName;
|
|
}
|
|
|
|
export function hasRecordedExternalHelpers(sourceFile: SourceFile) {
|
|
const parseNode = getOriginalNode(sourceFile, isSourceFile);
|
|
const emitNode = parseNode && parseNode.emitNode;
|
|
return !!emitNode && (!!emitNode.externalHelpersModuleName || !!emitNode.externalHelpers);
|
|
}
|
|
|
|
export function createExternalHelpersImportDeclarationIfNeeded(sourceFile: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStar?: boolean, hasImportDefault?: boolean) {
|
|
if (compilerOptions.importHelpers && isEffectiveExternalModule(sourceFile, compilerOptions)) {
|
|
let namedBindings: NamedImportBindings | undefined;
|
|
const moduleKind = getEmitModuleKind(compilerOptions);
|
|
if (moduleKind >= ModuleKind.ES2015 && moduleKind <= ModuleKind.ESNext) {
|
|
// use named imports
|
|
const helpers = getEmitHelpers(sourceFile);
|
|
if (helpers) {
|
|
const helperNames: string[] = [];
|
|
for (const helper of helpers) {
|
|
if (!helper.scoped) {
|
|
const importName = (helper as UnscopedEmitHelper).importName;
|
|
if (importName) {
|
|
pushIfUnique(helperNames, importName);
|
|
}
|
|
}
|
|
}
|
|
if (some(helperNames)) {
|
|
helperNames.sort(compareStringsCaseSensitive);
|
|
// Alias the imports if the names are used somewhere in the file.
|
|
// NOTE: We don't need to care about global import collisions as this is a module.
|
|
namedBindings = createNamedImports(
|
|
map(helperNames, name => isFileLevelUniqueName(sourceFile, name)
|
|
? createImportSpecifier(/*propertyName*/ undefined, createIdentifier(name))
|
|
: createImportSpecifier(createIdentifier(name), getUnscopedHelperName(name))
|
|
)
|
|
);
|
|
const parseNode = getOriginalNode(sourceFile, isSourceFile);
|
|
const emitNode = getOrCreateEmitNode(parseNode);
|
|
emitNode.externalHelpers = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// use a namespace import
|
|
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions, hasExportStarsToExportValues, hasImportStar || hasImportDefault);
|
|
if (externalHelpersModuleName) {
|
|
namedBindings = createNamespaceImport(externalHelpersModuleName);
|
|
}
|
|
}
|
|
if (namedBindings) {
|
|
const externalHelpersImportDeclaration = createImportDeclaration(
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
createImportClause(/*name*/ undefined, namedBindings),
|
|
createLiteral(externalHelpersModuleNameText)
|
|
);
|
|
addEmitFlags(externalHelpersImportDeclaration, EmitFlags.NeverApplyImportHelper);
|
|
return externalHelpersImportDeclaration;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions, hasExportStarsToExportValues?: boolean, hasImportStarOrImportDefault?: boolean) {
|
|
if (compilerOptions.importHelpers && isEffectiveExternalModule(node, compilerOptions)) {
|
|
const externalHelpersModuleName = getExternalHelpersModuleName(node);
|
|
if (externalHelpersModuleName) {
|
|
return externalHelpersModuleName;
|
|
}
|
|
|
|
const moduleKind = getEmitModuleKind(compilerOptions);
|
|
let create = (hasExportStarsToExportValues || (compilerOptions.esModuleInterop && hasImportStarOrImportDefault))
|
|
&& moduleKind !== ModuleKind.System
|
|
&& moduleKind < ModuleKind.ES2015;
|
|
if (!create) {
|
|
const helpers = getEmitHelpers(node);
|
|
if (helpers) {
|
|
for (const helper of helpers) {
|
|
if (!helper.scoped) {
|
|
create = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (create) {
|
|
const parseNode = getOriginalNode(node, isSourceFile);
|
|
const emitNode = getOrCreateEmitNode(parseNode);
|
|
return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the name of that target module from an import or export declaration
|
|
*/
|
|
export function getLocalNameForExternalImport(node: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile): Identifier | undefined {
|
|
const namespaceDeclaration = getNamespaceDeclarationNode(node);
|
|
if (namespaceDeclaration && !isDefaultImport(node)) {
|
|
const name = namespaceDeclaration.name;
|
|
return isGeneratedIdentifier(name) ? name : createIdentifier(getSourceTextOfNodeFromSourceFile(sourceFile, name) || idText(name));
|
|
}
|
|
if (node.kind === SyntaxKind.ImportDeclaration && node.importClause) {
|
|
return getGeneratedNameForNode(node);
|
|
}
|
|
if (node.kind === SyntaxKind.ExportDeclaration && node.moduleSpecifier) {
|
|
return getGeneratedNameForNode(node);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Get the name of a target module from an import/export declaration as should be written in the emitted output.
|
|
* The emitted output name can be different from the input if:
|
|
* 1. The module has a /// <amd-module name="<new name>" />
|
|
* 2. --out or --outFile is used, making the name relative to the rootDir
|
|
* 3- The containing SourceFile has an entry in renamedDependencies for the import as requested by some module loaders (e.g. System).
|
|
* Otherwise, a new StringLiteral node representing the module name will be returned.
|
|
*/
|
|
export function getExternalModuleNameLiteral(importNode: ImportDeclaration | ExportDeclaration | ImportEqualsDeclaration, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) {
|
|
const moduleName = getExternalModuleName(importNode)!; // TODO: GH#18217
|
|
if (moduleName.kind === SyntaxKind.StringLiteral) {
|
|
return tryGetModuleNameFromDeclaration(importNode, host, resolver, compilerOptions)
|
|
|| tryRenameExternalModule(<StringLiteral>moduleName, sourceFile)
|
|
|| getSynthesizedClone(<StringLiteral>moduleName);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Some bundlers (SystemJS builder) sometimes want to rename dependencies.
|
|
* Here we check if alternative name was provided for a given moduleName and return it if possible.
|
|
*/
|
|
function tryRenameExternalModule(moduleName: LiteralExpression, sourceFile: SourceFile) {
|
|
const rename = sourceFile.renamedDependencies && sourceFile.renamedDependencies.get(moduleName.text);
|
|
return rename && createLiteral(rename);
|
|
}
|
|
|
|
/**
|
|
* Get the name of a module as should be written in the emitted output.
|
|
* The emitted output name can be different from the input if:
|
|
* 1. The module has a /// <amd-module name="<new name>" />
|
|
* 2. --out or --outFile is used, making the name relative to the rootDir
|
|
* Otherwise, a new StringLiteral node representing the module name will be returned.
|
|
*/
|
|
export function tryGetModuleNameFromFile(file: SourceFile | undefined, host: EmitHost, options: CompilerOptions): StringLiteral | undefined {
|
|
if (!file) {
|
|
return undefined;
|
|
}
|
|
if (file.moduleName) {
|
|
return createLiteral(file.moduleName);
|
|
}
|
|
if (!file.isDeclarationFile && (options.out || options.outFile)) {
|
|
return createLiteral(getExternalModuleNameFromPath(host, file.fileName));
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function tryGetModuleNameFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration, host: EmitHost, resolver: EmitResolver, compilerOptions: CompilerOptions) {
|
|
return tryGetModuleNameFromFile(resolver.getExternalModuleFileFromDeclaration(declaration), host, compilerOptions);
|
|
}
|
|
|
|
/**
|
|
* Gets the initializer of an BindingOrAssignmentElement.
|
|
*/
|
|
export function getInitializerOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Expression | undefined {
|
|
if (isDeclarationBindingElement(bindingElement)) {
|
|
// `1` in `let { a = 1 } = ...`
|
|
// `1` in `let { a: b = 1 } = ...`
|
|
// `1` in `let { a: {b} = 1 } = ...`
|
|
// `1` in `let { a: [b] = 1 } = ...`
|
|
// `1` in `let [a = 1] = ...`
|
|
// `1` in `let [{a} = 1] = ...`
|
|
// `1` in `let [[a] = 1] = ...`
|
|
return bindingElement.initializer;
|
|
}
|
|
|
|
if (isPropertyAssignment(bindingElement)) {
|
|
// `1` in `({ a: b = 1 } = ...)`
|
|
// `1` in `({ a: {b} = 1 } = ...)`
|
|
// `1` in `({ a: [b] = 1 } = ...)`
|
|
const initializer = bindingElement.initializer;
|
|
return isAssignmentExpression(initializer, /*excludeCompoundAssignment*/ true)
|
|
? initializer.right
|
|
: undefined;
|
|
}
|
|
|
|
if (isShorthandPropertyAssignment(bindingElement)) {
|
|
// `1` in `({ a = 1 } = ...)`
|
|
return bindingElement.objectAssignmentInitializer;
|
|
}
|
|
|
|
if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) {
|
|
// `1` in `[a = 1] = ...`
|
|
// `1` in `[{a} = 1] = ...`
|
|
// `1` in `[[a] = 1] = ...`
|
|
return bindingElement.right;
|
|
}
|
|
|
|
if (isSpreadElement(bindingElement)) {
|
|
// Recovery consistent with existing emit.
|
|
return getInitializerOfBindingOrAssignmentElement(<BindingOrAssignmentElement>bindingElement.expression);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the name of an BindingOrAssignmentElement.
|
|
*/
|
|
export function getTargetOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementTarget | undefined {
|
|
if (isDeclarationBindingElement(bindingElement)) {
|
|
// `a` in `let { a } = ...`
|
|
// `a` in `let { a = 1 } = ...`
|
|
// `b` in `let { a: b } = ...`
|
|
// `b` in `let { a: b = 1 } = ...`
|
|
// `a` in `let { ...a } = ...`
|
|
// `{b}` in `let { a: {b} } = ...`
|
|
// `{b}` in `let { a: {b} = 1 } = ...`
|
|
// `[b]` in `let { a: [b] } = ...`
|
|
// `[b]` in `let { a: [b] = 1 } = ...`
|
|
// `a` in `let [a] = ...`
|
|
// `a` in `let [a = 1] = ...`
|
|
// `a` in `let [...a] = ...`
|
|
// `{a}` in `let [{a}] = ...`
|
|
// `{a}` in `let [{a} = 1] = ...`
|
|
// `[a]` in `let [[a]] = ...`
|
|
// `[a]` in `let [[a] = 1] = ...`
|
|
return bindingElement.name;
|
|
}
|
|
|
|
if (isObjectLiteralElementLike(bindingElement)) {
|
|
switch (bindingElement.kind) {
|
|
case SyntaxKind.PropertyAssignment:
|
|
// `b` in `({ a: b } = ...)`
|
|
// `b` in `({ a: b = 1 } = ...)`
|
|
// `{b}` in `({ a: {b} } = ...)`
|
|
// `{b}` in `({ a: {b} = 1 } = ...)`
|
|
// `[b]` in `({ a: [b] } = ...)`
|
|
// `[b]` in `({ a: [b] = 1 } = ...)`
|
|
// `b.c` in `({ a: b.c } = ...)`
|
|
// `b.c` in `({ a: b.c = 1 } = ...)`
|
|
// `b[0]` in `({ a: b[0] } = ...)`
|
|
// `b[0]` in `({ a: b[0] = 1 } = ...)`
|
|
return getTargetOfBindingOrAssignmentElement(<BindingOrAssignmentElement>bindingElement.initializer);
|
|
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
// `a` in `({ a } = ...)`
|
|
// `a` in `({ a = 1 } = ...)`
|
|
return bindingElement.name;
|
|
|
|
case SyntaxKind.SpreadAssignment:
|
|
// `a` in `({ ...a } = ...)`
|
|
return getTargetOfBindingOrAssignmentElement(<BindingOrAssignmentElement>bindingElement.expression);
|
|
}
|
|
|
|
// no target
|
|
return undefined;
|
|
}
|
|
|
|
if (isAssignmentExpression(bindingElement, /*excludeCompoundAssignment*/ true)) {
|
|
// `a` in `[a = 1] = ...`
|
|
// `{a}` in `[{a} = 1] = ...`
|
|
// `[a]` in `[[a] = 1] = ...`
|
|
// `a.b` in `[a.b = 1] = ...`
|
|
// `a[0]` in `[a[0] = 1] = ...`
|
|
return getTargetOfBindingOrAssignmentElement(<BindingOrAssignmentElement>bindingElement.left);
|
|
}
|
|
|
|
if (isSpreadElement(bindingElement)) {
|
|
// `a` in `[...a] = ...`
|
|
return getTargetOfBindingOrAssignmentElement(<BindingOrAssignmentElement>bindingElement.expression);
|
|
}
|
|
|
|
// `a` in `[a] = ...`
|
|
// `{a}` in `[{a}] = ...`
|
|
// `[a]` in `[[a]] = ...`
|
|
// `a.b` in `[a.b] = ...`
|
|
// `a[0]` in `[a[0]] = ...`
|
|
return bindingElement;
|
|
}
|
|
|
|
/**
|
|
* Determines whether an BindingOrAssignmentElement is a rest element.
|
|
*/
|
|
export function getRestIndicatorOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): BindingOrAssignmentElementRestIndicator | undefined {
|
|
switch (bindingElement.kind) {
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.BindingElement:
|
|
// `...` in `let [...a] = ...`
|
|
return bindingElement.dotDotDotToken;
|
|
|
|
case SyntaxKind.SpreadElement:
|
|
case SyntaxKind.SpreadAssignment:
|
|
// `...` in `[...a] = ...`
|
|
return bindingElement;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Gets the property name of a BindingOrAssignmentElement
|
|
*/
|
|
export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude<PropertyName, PrivateIdentifier> | undefined {
|
|
const propertyName = tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement);
|
|
Debug.assert(!!propertyName || isSpreadAssignment(bindingElement), "Invalid property name for binding element.");
|
|
return propertyName;
|
|
}
|
|
|
|
export function tryGetPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): Exclude<PropertyName, PrivateIdentifier> | undefined {
|
|
switch (bindingElement.kind) {
|
|
case SyntaxKind.BindingElement:
|
|
// `a` in `let { a: b } = ...`
|
|
// `[a]` in `let { [a]: b } = ...`
|
|
// `"a"` in `let { "a": b } = ...`
|
|
// `1` in `let { 1: b } = ...`
|
|
if (bindingElement.propertyName) {
|
|
const propertyName = bindingElement.propertyName;
|
|
if (isPrivateIdentifier(propertyName)) {
|
|
return Debug.failBadSyntaxKind(propertyName);
|
|
}
|
|
return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression)
|
|
? propertyName.expression
|
|
: propertyName;
|
|
}
|
|
|
|
break;
|
|
|
|
case SyntaxKind.PropertyAssignment:
|
|
// `a` in `({ a: b } = ...)`
|
|
// `[a]` in `({ [a]: b } = ...)`
|
|
// `"a"` in `({ "a": b } = ...)`
|
|
// `1` in `({ 1: b } = ...)`
|
|
if (bindingElement.name) {
|
|
const propertyName = bindingElement.name;
|
|
if (isPrivateIdentifier(propertyName)) {
|
|
return Debug.failBadSyntaxKind(propertyName);
|
|
}
|
|
return isComputedPropertyName(propertyName) && isStringOrNumericLiteral(propertyName.expression)
|
|
? propertyName.expression
|
|
: propertyName;
|
|
}
|
|
|
|
break;
|
|
|
|
case SyntaxKind.SpreadAssignment:
|
|
// `a` in `({ ...a } = ...)`
|
|
if (bindingElement.name && isPrivateIdentifier(bindingElement.name)) {
|
|
return Debug.failBadSyntaxKind(bindingElement.name);
|
|
}
|
|
return bindingElement.name;
|
|
}
|
|
|
|
const target = getTargetOfBindingOrAssignmentElement(bindingElement);
|
|
if (target && isPropertyName(target)) {
|
|
return target;
|
|
}
|
|
}
|
|
|
|
function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral {
|
|
const kind = node.kind;
|
|
return kind === SyntaxKind.StringLiteral
|
|
|| kind === SyntaxKind.NumericLiteral;
|
|
}
|
|
|
|
/**
|
|
* Gets the elements of a BindingOrAssignmentPattern
|
|
*/
|
|
export function getElementsOfBindingOrAssignmentPattern(name: BindingOrAssignmentPattern): readonly BindingOrAssignmentElement[] {
|
|
switch (name.kind) {
|
|
case SyntaxKind.ObjectBindingPattern:
|
|
case SyntaxKind.ArrayBindingPattern:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
// `a` in `{a}`
|
|
// `a` in `[a]`
|
|
return <readonly BindingOrAssignmentElement[]>name.elements;
|
|
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
// `a` in `{a}`
|
|
return <readonly BindingOrAssignmentElement[]>name.properties;
|
|
}
|
|
}
|
|
|
|
export function convertToArrayAssignmentElement(element: BindingOrAssignmentElement) {
|
|
if (isBindingElement(element)) {
|
|
if (element.dotDotDotToken) {
|
|
Debug.assertNode(element.name, isIdentifier);
|
|
return setOriginalNode(setTextRange(createSpread(element.name), element), element);
|
|
}
|
|
const expression = convertToAssignmentElementTarget(element.name);
|
|
return element.initializer
|
|
? setOriginalNode(
|
|
setTextRange(
|
|
createAssignment(expression, element.initializer),
|
|
element
|
|
),
|
|
element
|
|
)
|
|
: expression;
|
|
}
|
|
Debug.assertNode(element, isExpression);
|
|
return <Expression>element;
|
|
}
|
|
|
|
export function convertToObjectAssignmentElement(element: BindingOrAssignmentElement) {
|
|
if (isBindingElement(element)) {
|
|
if (element.dotDotDotToken) {
|
|
Debug.assertNode(element.name, isIdentifier);
|
|
return setOriginalNode(setTextRange(createSpreadAssignment(element.name), element), element);
|
|
}
|
|
if (element.propertyName) {
|
|
const expression = convertToAssignmentElementTarget(element.name);
|
|
return setOriginalNode(setTextRange(createPropertyAssignment(element.propertyName, element.initializer ? createAssignment(expression, element.initializer) : expression), element), element);
|
|
}
|
|
Debug.assertNode(element.name, isIdentifier);
|
|
return setOriginalNode(setTextRange(createShorthandPropertyAssignment(element.name, element.initializer), element), element);
|
|
}
|
|
Debug.assertNode(element, isObjectLiteralElementLike);
|
|
return <ObjectLiteralElementLike>element;
|
|
}
|
|
|
|
export function convertToAssignmentPattern(node: BindingOrAssignmentPattern): AssignmentPattern {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ArrayBindingPattern:
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return convertToArrayAssignmentPattern(node);
|
|
|
|
case SyntaxKind.ObjectBindingPattern:
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return convertToObjectAssignmentPattern(node);
|
|
}
|
|
}
|
|
|
|
export function convertToObjectAssignmentPattern(node: ObjectBindingOrAssignmentPattern) {
|
|
if (isObjectBindingPattern(node)) {
|
|
return setOriginalNode(
|
|
setTextRange(
|
|
createObjectLiteral(map(node.elements, convertToObjectAssignmentElement)),
|
|
node
|
|
),
|
|
node
|
|
);
|
|
}
|
|
Debug.assertNode(node, isObjectLiteralExpression);
|
|
return node;
|
|
}
|
|
|
|
export function convertToArrayAssignmentPattern(node: ArrayBindingOrAssignmentPattern) {
|
|
if (isArrayBindingPattern(node)) {
|
|
return setOriginalNode(
|
|
setTextRange(
|
|
createArrayLiteral(map(node.elements, convertToArrayAssignmentElement)),
|
|
node
|
|
),
|
|
node
|
|
);
|
|
}
|
|
Debug.assertNode(node, isArrayLiteralExpression);
|
|
return node;
|
|
}
|
|
|
|
export function convertToAssignmentElementTarget(node: BindingOrAssignmentElementTarget): Expression {
|
|
if (isBindingPattern(node)) {
|
|
return convertToAssignmentPattern(node);
|
|
}
|
|
|
|
Debug.assertNode(node, isExpression);
|
|
return node;
|
|
}
|
|
}
|