diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 796c9603423..64004d519e1 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2737,7 +2737,11 @@ namespace ts { const operatorTokenKind = node.operatorToken.kind; const leftKind = node.left.kind; - if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { + if (operatorTokenKind === SyntaxKind.BarGreaterThanToken) { + // pipeline is ESNext + transformFlags |= TransformFlags.AssertESNext; + } + else if (operatorTokenKind === SyntaxKind.EqualsToken && leftKind === SyntaxKind.ObjectLiteralExpression) { // Destructuring object assignments with are ES2015 syntax // and possibly ESNext if they contain rest transformFlags |= TransformFlags.AssertESNext | TransformFlags.AssertES2015 | TransformFlags.AssertDestructuringAssignment; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58248a8a9ee..2caee634f8a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6319,7 +6319,7 @@ namespace ts { if (iife) { return !node.type && !node.dotDotDotToken && - indexOf((node.parent as SignatureDeclaration).parameters, node) >= iife.arguments.length; + indexOf((node.parent as SignatureDeclaration).parameters, node) >= (isPipelineExpression(iife) ? 1 : iife.arguments.length); } return false; @@ -6430,7 +6430,7 @@ namespace ts { // Record a new minimum argument count if this is not an optional parameter const isOptionalParameter = param.initializer || param.questionToken || param.dotDotDotToken || - iife && parameters.length > iife.arguments.length && !param.type || + iife && parameters.length > (isPipelineExpression(iife) ? 1 : iife.arguments.length) && !param.type || isJSDocOptionalParameter(param) || isUntypedSignatureInJSFile; if (!isOptionalParameter) { @@ -12371,7 +12371,8 @@ namespace ts { const parent = node.parent; return parent.kind === SyntaxKind.PropertyAccessExpression || parent.kind === SyntaxKind.CallExpression && (parent).expression === node || - parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node; + parent.kind === SyntaxKind.ElementAccessExpression && (parent).expression === node || + isPipelineExpression(parent) && parent.right === node; } function typeHasNullableConstraint(type: Type) { @@ -13089,20 +13090,21 @@ namespace ts { const func = parameter.parent; if (isContextSensitiveFunctionOrObjectLiteralMethod(func)) { const iife = getImmediatelyInvokedFunctionExpression(func); - if (iife && iife.arguments) { + if (iife && (isPipelineExpression(iife) || iife.arguments)) { + const args = getEffectiveCallArguments(iife); const indexOfParameter = indexOf(func.parameters, parameter); if (parameter.dotDotDotToken) { const restTypes: Type[] = []; - for (let i = indexOfParameter; i < iife.arguments.length; i++) { - restTypes.push(getWidenedLiteralType(checkExpression(iife.arguments[i]))); + for (let i = indexOfParameter; i < args.length; i++) { + restTypes.push(getWidenedLiteralType(checkExpression(args[i]))); } return restTypes.length ? createArrayType(getUnionType(restTypes)) : undefined; } const links = getNodeLinks(iife); const cached = links.resolvedSignature; links.resolvedSignature = anySignature; - const type = indexOfParameter < iife.arguments.length ? - getWidenedLiteralType(checkExpression(iife.arguments[indexOfParameter])) : + const type = indexOfParameter < args.length ? + getWidenedLiteralType(checkExpression(args[indexOfParameter])) : parameter.initializer ? undefined : undefinedWideningType; links.resolvedSignature = cached; return type; @@ -13291,6 +13293,11 @@ namespace ts { return getContextualType(binaryExpression); } } + else if (operator === SyntaxKind.BarGreaterThanToken) { + if (node === binaryExpression.left) { + return getContextualTypeForArgument(binaryExpression, node); + } + } return undefined; } @@ -15296,7 +15303,10 @@ namespace ts { } if (node.kind === SyntaxKind.TaggedTemplateExpression) { - checkExpression((node).template); + checkExpression(node.template); + } + else if (node.kind === SyntaxKind.BinaryExpression) { + checkExpression(node.left); } else if (node.kind !== SyntaxKind.Decorator) { forEach((node).arguments, argument => { @@ -15415,6 +15425,10 @@ namespace ts { typeArguments = undefined; argCount = getEffectiveArgumentCount(node, /*args*/ undefined, signature); } + else if (node.kind === SyntaxKind.BinaryExpression) { + typeArguments = undefined; + argCount = getEffectiveArgumentCount(node, args, signature); + } else { const callExpression = node; if (!callExpression.arguments) { @@ -15690,8 +15704,12 @@ namespace ts { * Returns the this argument in calls like x.f(...) and x[f](...). Undefined otherwise. */ function getThisArgumentOfCall(node: CallLikeExpression): LeftHandSideExpression { - if (node.kind === SyntaxKind.CallExpression) { - const callee = (node).expression; + let callee = node.kind === SyntaxKind.CallExpression ? node.expression : + node.kind === SyntaxKind.Decorator ? node.expression : + node.kind === SyntaxKind.TaggedTemplateExpression ? node.tag : + node.kind === SyntaxKind.BinaryExpression ? node.right : + undefined; + if (callee) { if (callee.kind === SyntaxKind.PropertyAccessExpression) { return (callee as PropertyAccessExpression).expression; } @@ -15712,15 +15730,18 @@ namespace ts { */ function getEffectiveCallArguments(node: CallLikeExpression): ReadonlyArray { if (node.kind === SyntaxKind.TaggedTemplateExpression) { - const template = (node).template; + const template = node.template; const args: Expression[] = [undefined]; if (template.kind === SyntaxKind.TemplateExpression) { - forEach((template).templateSpans, span => { + forEach(template.templateSpans, span => { args.push(span.expression); }); } return args; } + else if (node.kind === SyntaxKind.BinaryExpression) { + return [node.left]; + } else if (node.kind === SyntaxKind.Decorator) { // For a decorator, we return undefined as we will determine // the number and types of arguments for a decorator using @@ -16008,21 +16029,22 @@ namespace ts { } } + function getEffectiveTypeArguments(node: CallLikeExpression): NodeArray { + if (node.kind === SyntaxKind.CallExpression || node.kind === SyntaxKind.NewExpression) { + const typeArguments = node.typeArguments; + // We already perform checking on the type arguments on the class declaration itself. + if (node.expression.kind !== SyntaxKind.SuperKeyword) { + forEach(typeArguments, checkSourceElement); + } + return typeArguments; + } + } + function resolveCall(node: CallLikeExpression, signatures: Signature[], candidatesOutArray: Signature[], fallbackError?: DiagnosticMessage): Signature { const isTaggedTemplate = node.kind === SyntaxKind.TaggedTemplateExpression; const isDecorator = node.kind === SyntaxKind.Decorator; const isJsxOpeningOrSelfClosingElement = isJsxOpeningLikeElement(node); - - let typeArguments: ReadonlyArray; - - if (!isTaggedTemplate && !isDecorator && !isJsxOpeningOrSelfClosingElement) { - typeArguments = (node).typeArguments; - - // We already perform checking on the type arguments on the class declaration itself. - if ((node).expression.kind !== SyntaxKind.SuperKeyword) { - forEach(typeArguments, checkSourceElement); - } - } + const typeArguments = getEffectiveTypeArguments(node); const candidates = candidatesOutArray || []; // reorderCandidates fills up the candidates array directly @@ -16578,6 +16600,27 @@ namespace ts { signature.parameters.length < getEffectiveArgumentCount(decorator, /*args*/ undefined, signature)); } + function resolvePipelineExpression(node: PipelineExpression, candidatesOutArray: Signature[]): Signature { + const type = checkNonNullExpression(node.right); + const apparentType = getApparentType(type); + if (apparentType === unknownType) { + return resolveErrorCall(node); + } + + const callSignatures = getSignaturesOfType(apparentType, SignatureKind.Call); + const constructSignatures = getSignaturesOfType(apparentType, SignatureKind.Construct); + if (isUntypedFunctionCall(type, apparentType, callSignatures.length, constructSignatures.length)) { + return resolveUntypedCall(node); + } + + if (!callSignatures.length) { + error(node, Diagnostics.Cannot_invoke_an_expression_whose_type_lacks_a_call_signature_Type_0_has_no_compatible_call_signatures, typeToString(apparentType)); + return resolveErrorCall(node); + } + + return resolveCall(node, callSignatures, candidatesOutArray); + } + /** * This function is similar to getResolvedSignature but is exclusively for trying to resolve JSX stateless-function component. * The main reason we have to use this function instead of getResolvedSignature because, the caller of this function will already check the type of openingLikeElement's tagName @@ -16625,17 +16668,19 @@ namespace ts { function resolveSignature(node: CallLikeExpression, candidatesOutArray?: Signature[]): Signature { switch (node.kind) { case SyntaxKind.CallExpression: - return resolveCallExpression(node, candidatesOutArray); + return resolveCallExpression(node, candidatesOutArray); case SyntaxKind.NewExpression: - return resolveNewExpression(node, candidatesOutArray); + return resolveNewExpression(node, candidatesOutArray); case SyntaxKind.TaggedTemplateExpression: - return resolveTaggedTemplateExpression(node, candidatesOutArray); + return resolveTaggedTemplateExpression(node, candidatesOutArray); case SyntaxKind.Decorator: - return resolveDecorator(node, candidatesOutArray); + return resolveDecorator(node, candidatesOutArray); + case SyntaxKind.BinaryExpression: + return resolvePipelineExpression(node, candidatesOutArray); case SyntaxKind.JsxOpeningElement: case SyntaxKind.JsxSelfClosingElement: // This code-path is called by language service - return resolveStatelessJsxOpeningLikeElement(node, checkExpression((node).tagName), candidatesOutArray) || unknownSignature; + return resolveStatelessJsxOpeningLikeElement(node, checkExpression(node.tagName), candidatesOutArray) || unknownSignature; } Debug.assertNever(node, "Branch in 'resolveSignature' should be unreachable."); } @@ -17828,6 +17873,9 @@ namespace ts { } function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) { + if (node.operatorToken.kind === SyntaxKind.BarGreaterThanToken) { + return checkPipelineExpression(node); + } return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node); } @@ -18029,6 +18077,12 @@ namespace ts { } } + function checkPipelineExpression(node: PipelineExpression) { + checkGrammarPipelineExpression(node); + const signature = getResolvedSignature(node); + return getReturnTypeOfSignature(signature); + } + function isYieldExpressionInClass(node: YieldExpression): boolean { let current: Node = node; let parent = node.parent; @@ -25282,6 +25336,12 @@ namespace ts { } } + function checkGrammarPipelineExpression(node: PipelineExpression) { + if (!compilerOptions.experimentalPipeline) { + return grammarErrorOnNode(node.operatorToken, Diagnostics.Pipeline_expressions_are_part_of_a_stage_1_ECMAScript_proposal_and_are_subject_to_change_in_future_releases_Set_the_experimentalPipeline_option_to_remove_this_warning); + } + } + function hasParseDiagnostics(sourceFile: SourceFile): boolean { return sourceFile.parseDiagnostics.length > 0; } diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index af0697a12d9..cfff03b4b2a 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -440,6 +440,12 @@ namespace ts { category: Diagnostics.Experimental_Options, description: Diagnostics.Enables_experimental_support_for_emitting_type_metadata_for_decorators }, + { + name: "experimentalPipeline", + type: "boolean", + category: Diagnostics.Experimental_Options, + description: Diagnostics.Enables_experimental_support_for_the_stage_1_ECMAScript_pipeline_operator + }, // Advanced { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 17c23660820..e92e43d8926 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -831,6 +831,11 @@ "category": "Error", "code": 1254 }, + "Pipeline expressions are part of a stage 1 ECMAScript proposal and are subject to change in future releases. Set the 'experimentalPipeline' option to remove this warning.": { + "category": "Error", + "code": 1255 + }, + "'with' statements are not allowed in an async function block.": { "category": "Error", "code": 1300 @@ -3318,6 +3323,12 @@ "category": "Message", "code": 6186 }, + "Enables experimental support for the stage-1 ECMAScript pipeline operator.": { + "category": "Message", + "code": 6187 + }, + + "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", "code": 7005 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ebdf390f5b2..986269c250a 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3549,21 +3549,23 @@ namespace ts { function getBinaryOperatorPrecedence(): number { switch (token()) { - case SyntaxKind.BarBarToken: + case SyntaxKind.BarGreaterThanToken: return 1; - case SyntaxKind.AmpersandAmpersandToken: + case SyntaxKind.BarBarToken: return 2; - case SyntaxKind.BarToken: + case SyntaxKind.AmpersandAmpersandToken: return 3; - case SyntaxKind.CaretToken: + case SyntaxKind.BarToken: return 4; - case SyntaxKind.AmpersandToken: + case SyntaxKind.CaretToken: return 5; + case SyntaxKind.AmpersandToken: + return 6; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - return 6; + return 7; case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanEqualsToken: @@ -3571,20 +3573,20 @@ namespace ts { case SyntaxKind.InstanceOfKeyword: case SyntaxKind.InKeyword: case SyntaxKind.AsKeyword: - return 7; + return 8; case SyntaxKind.LessThanLessThanToken: case SyntaxKind.GreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - return 8; + return 9; case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: - return 9; + return 10; case SyntaxKind.AsteriskToken: case SyntaxKind.SlashToken: case SyntaxKind.PercentToken: - return 10; - case SyntaxKind.AsteriskAsteriskToken: return 11; + case SyntaxKind.AsteriskAsteriskToken: + return 12; } // -1 is lower than all other precedences. Returning it will cause binary expression diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index b19a1466328..9f2c8afd691 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -168,6 +168,7 @@ namespace ts { "~": SyntaxKind.TildeToken, "&&": SyntaxKind.AmpersandAmpersandToken, "||": SyntaxKind.BarBarToken, + "|>": SyntaxKind.BarGreaterThanToken, "?": SyntaxKind.QuestionToken, ":": SyntaxKind.ColonToken, "=": SyntaxKind.EqualsToken, @@ -1587,6 +1588,9 @@ namespace ts { if (text.charCodeAt(pos + 1) === CharacterCodes.equals) { return pos += 2, token = SyntaxKind.BarEqualsToken; } + if (text.charCodeAt(pos + 1) === CharacterCodes.greaterThan) { + return pos += 2, token = SyntaxKind.BarGreaterThanToken; + } pos++; return token = SyntaxKind.BarToken; case CharacterCodes.closeBrace: diff --git a/src/compiler/transformers/esnext.ts b/src/compiler/transformers/esnext.ts index 5b4c12b4f23..1cf107ad843 100644 --- a/src/compiler/transformers/esnext.ts +++ b/src/compiler/transformers/esnext.ts @@ -225,6 +225,31 @@ namespace ts { return visitEachChild(node, visitor, context); } + function transformPipelineExpressionWorker(node: PipelineExpression, pipelineVariable: Identifier, expressions: Expression[], top: boolean) { + if (isPipelineExpression(node.left)) { + transformPipelineExpressionWorker(node.left, pipelineVariable, expressions, false); + } + else { + expressions.push(createAssignment(pipelineVariable, visitNode(node.left, visitor, isExpression))); + } + + const right = visitNode(node.right, visitor, isExpression); + const call = createCall(right, /*typeArguments*/ undefined, [pipelineVariable]); + setSourceMapRange(call, node); + setCommentRange(call, node); + expressions.push(top ? call : createAssignment(pipelineVariable, call)); + } + + function transformPipelineExpression(node: PipelineExpression) { + const pipelineVariable = createTempVariable(hoistVariableDeclaration); + const expressions: Expression[] = []; + transformPipelineExpressionWorker(node, pipelineVariable, expressions, /*top*/ true); + const transformed = inlineExpressions(expressions); + setSourceMapRange(transformed, node); + setCommentRange(transformed, node); + return transformed; + } + /** * Visits a BinaryExpression that contains a destructuring assignment. * @@ -247,6 +272,9 @@ namespace ts { visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression) ); } + else if (node.operatorToken.kind === SyntaxKind.BarGreaterThanToken) { + return transformPipelineExpression(node); + } return visitEachChild(node, visitor, context); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 578c2d23c4f..158f8c111a4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -109,6 +109,7 @@ namespace ts { TildeToken, AmpersandAmpersandToken, BarBarToken, + BarGreaterThanToken, QuestionToken, ColonToken, AtToken, @@ -1279,6 +1280,11 @@ namespace ts { | LogicalOperator ; + export type PipelineOperatorOrHigher + = LogicalOperatorOrHigher + | SyntaxKind.BarGreaterThanToken + ; + // see: https://tc39.github.io/ecma262/#prod-AssignmentOperator export type CompoundAssignmentOperator = SyntaxKind.PlusEqualsToken @@ -1303,7 +1309,7 @@ namespace ts { // see: https://tc39.github.io/ecma262/#prod-AssignmentExpression export type AssignmentOperatorOrHigher - = LogicalOperatorOrHigher + = PipelineOperatorOrHigher | AssignmentOperator ; @@ -1315,13 +1321,18 @@ namespace ts { export type BinaryOperatorToken = Token; - export interface BinaryExpression extends Expression, Declaration { + export interface BinaryExpression extends Expression, Declaration { kind: SyntaxKind.BinaryExpression; left: Expression; operatorToken: BinaryOperatorToken; right: Expression; } + /*@internal*/ + export interface PipelineExpression extends BinaryExpression { + operatorToken: Token; + } + export type AssignmentOperatorToken = Token; export interface AssignmentExpression extends BinaryExpression { @@ -1581,7 +1592,7 @@ namespace ts { template: TemplateLiteral; } - export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement; + export type CallLikeExpression = CallExpression | NewExpression | TaggedTemplateExpression | Decorator | PipelineExpression | JsxOpeningLikeElement; export interface AsExpression extends Expression { kind: SyntaxKind.AsExpression; @@ -3673,6 +3684,7 @@ namespace ts { emitBOM?: boolean; emitDecoratorMetadata?: boolean; experimentalDecorators?: boolean; + experimentalPipeline?: boolean; forceConsistentCasingInFileNames?: boolean; /*@internal*/help?: boolean; importHelpers?: boolean; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index f0eb394adb7..6b386989663 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1106,7 +1106,7 @@ namespace ts { } } - export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression { + export function getImmediatelyInvokedFunctionExpression(func: Node): CallExpression | PipelineExpression | undefined { if (func.kind === SyntaxKind.FunctionExpression || func.kind === SyntaxKind.ArrowFunction) { let prev = func; let parent = func.parent; @@ -1117,6 +1117,9 @@ namespace ts { if (parent.kind === SyntaxKind.CallExpression && (parent as CallExpression).expression === prev) { return parent as CallExpression; } + if (isPipelineExpression(parent) && parent.right === prev) { + return parent; + } } } @@ -2155,49 +2158,49 @@ namespace ts { case SyntaxKind.TemplateExpression: case SyntaxKind.ParenthesizedExpression: case SyntaxKind.OmittedExpression: - return 19; + return 20; case SyntaxKind.TaggedTemplateExpression: case SyntaxKind.PropertyAccessExpression: case SyntaxKind.ElementAccessExpression: - return 18; + return 19; case SyntaxKind.NewExpression: - return hasArguments ? 18 : 17; + return hasArguments ? 19 : 18; case SyntaxKind.CallExpression: - return 17; + return 18; case SyntaxKind.PostfixUnaryExpression: - return 16; + return 17; case SyntaxKind.PrefixUnaryExpression: case SyntaxKind.TypeOfExpression: case SyntaxKind.VoidExpression: case SyntaxKind.DeleteExpression: case SyntaxKind.AwaitExpression: - return 15; + return 16; case SyntaxKind.BinaryExpression: switch (operatorKind) { case SyntaxKind.ExclamationToken: case SyntaxKind.TildeToken: - return 15; + return 16; case SyntaxKind.AsteriskAsteriskToken: case SyntaxKind.AsteriskToken: case SyntaxKind.SlashToken: case SyntaxKind.PercentToken: - return 14; + return 15; case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: - return 13; + return 14; case SyntaxKind.LessThanLessThanToken: case SyntaxKind.GreaterThanGreaterThanToken: case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - return 12; + return 13; case SyntaxKind.LessThanToken: case SyntaxKind.LessThanEqualsToken: @@ -2205,27 +2208,30 @@ namespace ts { case SyntaxKind.GreaterThanEqualsToken: case SyntaxKind.InKeyword: case SyntaxKind.InstanceOfKeyword: - return 11; + return 12; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.EqualsEqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: case SyntaxKind.ExclamationEqualsEqualsToken: - return 10; + return 11; case SyntaxKind.AmpersandToken: - return 9; + return 10; case SyntaxKind.CaretToken: - return 8; + return 9; case SyntaxKind.BarToken: - return 7; + return 8; case SyntaxKind.AmpersandAmpersandToken: - return 6; + return 7; case SyntaxKind.BarBarToken: + return 6; + + case SyntaxKind.BarGreaterThanToken: return 5; case SyntaxKind.EqualsToken: @@ -4520,6 +4526,11 @@ namespace ts { return node.kind === SyntaxKind.BinaryExpression; } + export function isPipelineExpression(node: Node): node is PipelineExpression { + return isBinaryExpression(node) + && node.operatorToken.kind === SyntaxKind.BarGreaterThanToken; + } + export function isConditionalExpression(node: Node): node is ConditionalExpression { return node.kind === SyntaxKind.ConditionalExpression; } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 7d46630e227..d818f0691ff 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1549,6 +1549,14 @@ namespace ts { assertOptionalNode) : noop; + export const assertToken = shouldAssert(AssertionLevel.Normal) + ? (node: Node, kind: SyntaxKind, message?: string): void => assert( + kind === undefined || node.kind === kind, + message || "Unexpected node.", + () => `Node ${formatSyntaxKind(node.kind)} was not a '${formatSyntaxKind(kind)}' token.`, + assertOptionalToken) + : noop; + export const assertOptionalToken = shouldAssert(AssertionLevel.Normal) ? (node: Node, kind: SyntaxKind, message?: string): void => assert( kind === undefined || node === undefined || node.kind === kind,