From b092ddde62b8d7f223809723b43965b5a9e34b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 15 Jan 2024 16:43:11 +0100 Subject: [PATCH] Simplify pipeline parsing with `using` declarations --- babel.config.js | 2 + eslint.config.js | 5 +- package.json | 1 + .../babel-parser/src/parser/expression.ts | 129 ++++-------------- packages/babel-parser/src/parser/statement.ts | 105 +++++++------- packages/babel-parser/src/tokenizer/index.ts | 12 ++ packages/babel-parser/src/tokenizer/state.ts | 24 +--- yarn.lock | 24 ++++ 8 files changed, 126 insertions(+), 176 deletions(-) diff --git a/babel.config.js b/babel.config.js index 2cf012a204d0..164e23c20d63 100644 --- a/babel.config.js +++ b/babel.config.js @@ -182,6 +182,8 @@ module.exports = function (api) { plugins: [ ["@babel/transform-object-rest-spread", { useBuiltIns: true }], + "@babel/plugin-proposal-explicit-resource-management", + convertESM ? "@babel/transform-export-namespace-from" : null, pluginPackageJsonMacro, diff --git a/eslint.config.js b/eslint.config.js index 97adfe1799c3..7a0ae74a7eaa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -97,7 +97,10 @@ module.exports = [ rules: { ...config.rules, "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-unused-vars": [ + "error", + { varsIgnorePattern: "^_" }, + ], "no-dupe-class-members": "off", "@typescript-eslint/no-dupe-class-members": "error", "no-undef": "off", diff --git a/package.json b/package.json index 06133550d631..98a4a0246667 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@babel/eslint-parser": "workspace:^", "@babel/eslint-plugin-development": "workspace:^", "@babel/eslint-plugin-development-internal": "workspace:^", + "@babel/plugin-proposal-explicit-resource-management": "8.0.0-alpha.5", "@babel/plugin-transform-dynamic-import": "8.0.0-alpha.5", "@babel/plugin-transform-export-namespace-from": "8.0.0-alpha.5", "@babel/plugin-transform-modules-commonjs": "8.0.0-alpha.5", diff --git a/packages/babel-parser/src/parser/expression.ts b/packages/babel-parser/src/parser/expression.ts index 6b47288c2e35..368a40e3a195 100644 --- a/packages/babel-parser/src/parser/expression.ts +++ b/packages/babel-parser/src/parser/expression.ts @@ -514,26 +514,27 @@ export default abstract class ExpressionParser extends LValParser { switch (op) { case tt.pipeline: switch (this.getPluginOption("pipelineOperator", "proposal")) { - case "hack": - return this.withTopicBindingContext(() => { - return this.parseHackPipeBody(); - }); - - case "smart": - return this.withTopicBindingContext(() => { - if (this.prodParam.hasYield && this.isContextual(tt._yield)) { - throw this.raise(Errors.PipeBodyIsTighter, this.state.startLoc); - } - return this.parseSmartPipelineBodyInStyle( - this.parseExprOpBaseRightExpr(op, prec), - startLoc, - ); - }); + case "hack": { + using _ = this.withState("topicReferenceUsage", { used: false }); + return this.parseHackPipeBody(); + } + + case "smart": { + if (this.prodParam.hasYield && this.isContextual(tt._yield)) { + throw this.raise(Errors.PipeBodyIsTighter, this.state.startLoc); + } - case "fsharp": - return this.withSoloAwaitPermittingContext(() => { - return this.parseFSharpPipelineBody(prec); - }); + using _ = this.withState("topicReferenceUsage", { used: false }); + return this.parseSmartPipelineBodyInStyle( + this.parseExprOpBaseRightExpr(op, prec), + startLoc, + ); + } + + case "fsharp": { + using _ = this.withState("soloAwait", true); + return this.parseFSharpPipelineBody(prec); + } } // Falls through. @@ -573,7 +574,7 @@ export default abstract class ExpressionParser extends LValParser { type: body.type as UnparenthesizedPipeBodyTypes, }); } - if (!this.topicReferenceWasUsedInCurrentContext()) { + if (!this.state.topicReferenceUsage.used) { // A Hack pipe body must use the topic reference at least once. this.raise(Errors.PipeTopicUnused, startLoc); } @@ -1435,7 +1436,7 @@ export default abstract class ExpressionParser extends LValParser { // as enforced by testTopicReferenceConfiguration. "TopicReference"; - if (!this.topicReferenceIsAllowedInCurrentContext()) { + if (!this.state.topicReferenceUsage) { this.raise( // The topic reference is not allowed in the current context: // it is outside of a pipe body. @@ -1446,12 +1447,12 @@ export default abstract class ExpressionParser extends LValParser { Errors.PipeTopicUnbound, startLoc, ); + } else { + // Register the topic reference so that its pipe body knows + // that its topic was used at least once. + this.state.topicReferenceUsage.used = true; } - // Register the topic reference so that its pipe body knows - // that its topic was used at least once. - this.registerTopicReference(); - return this.finishNode(node, nodeType); } else { // The token does not match the plugin’s configuration. @@ -3027,74 +3028,19 @@ export default abstract class ExpressionParser extends LValParser { } // A topic-style smart-mix pipe body must use the topic reference at least once. - if (!this.topicReferenceWasUsedInCurrentContext()) { + if (!this.state.topicReferenceUsage.used) { this.raise(Errors.PipelineTopicUnused, startLoc); } } - // Enable topic references from outer contexts within Hack-style pipe bodies. - // The function modifies the parser's topic-context state to enable or disable - // the use of topic references. - // The function then calls a callback, then resets the parser - // to the old topic-context state that it had before the function was called. - - withTopicBindingContext(callback: () => T): T { - const outerContextTopicState = this.state.topicContext; - this.state.topicContext = { - // Enable the use of the primary topic reference. - maxNumOfResolvableTopics: 1, - // Hide the use of any topic references from outer contexts. - maxTopicIndex: null, - }; - - try { - return callback(); - } finally { - this.state.topicContext = outerContextTopicState; - } - } - // This helper method is used only with the deprecated smart-mix pipe proposal. // Disables topic references from outer contexts within syntax constructs // such as the bodies of iteration statements. - // The function modifies the parser's topic-context state to enable or disable - // the use of topic references with the smartPipelines plugin. They then run a - // callback, then they reset the parser to the old topic-context state that it - // had before the function was called. - withSmartMixTopicForbiddingContext(callback: () => T): T { + withSmartMixTopicForbiddingContext(): Disposable | undefined { if (this.hasPlugin(["pipelineOperator", { proposal: "smart" }])) { // Reset the parser’s topic context only if the smart-mix pipe proposal is active. - const outerContextTopicState = this.state.topicContext; - this.state.topicContext = { - // Disable the use of the primary topic reference. - maxNumOfResolvableTopics: 0, - // Hide the use of any topic references from outer contexts. - maxTopicIndex: null, - }; - - try { - return callback(); - } finally { - this.state.topicContext = outerContextTopicState; - } - } else { - // If the pipe proposal is "minimal", "fsharp", or "hack", - // or if no pipe proposal is active, - // then the callback result is returned - // without touching any extra parser state. - return callback(); - } - } - - withSoloAwaitPermittingContext(callback: () => T): T { - const outerContextSoloAwaitState = this.state.soloAwait; - this.state.soloAwait = true; - - try { - return callback(); - } finally { - this.state.soloAwait = outerContextSoloAwaitState; + return this.withState("topicReferenceUsage", null); } } @@ -3126,23 +3072,6 @@ export default abstract class ExpressionParser extends LValParser { return callback(); } - // Register the use of a topic reference within the current - // topic-binding context. - registerTopicReference(): void { - this.state.topicContext.maxTopicIndex = 0; - } - - topicReferenceIsAllowedInCurrentContext(): boolean { - return this.state.topicContext.maxNumOfResolvableTopics >= 1; - } - - topicReferenceWasUsedInCurrentContext(): boolean { - return ( - this.state.topicContext.maxTopicIndex != null && - this.state.topicContext.maxTopicIndex >= 0 - ); - } - parseFSharpPipelineBody(this: Parser, prec: number): N.Expression { const startLoc = this.state.startLoc; diff --git a/packages/babel-parser/src/parser/statement.ts b/packages/babel-parser/src/parser/statement.ts index 2c0c17c9afd6..85bbcdb37273 100644 --- a/packages/babel-parser/src/parser/statement.ts +++ b/packages/babel-parser/src/parser/statement.ts @@ -874,15 +874,15 @@ export default abstract class StatementParser extends ExpressionParser { this.next(); this.state.labels.push(loopLabel); - // Parse the loop body's body. - node.body = + { // For the smartPipelines plugin: Disable topic references from outer // contexts within the loop body. They are permitted in test expressions, // outside of the loop body. - this.withSmartMixTopicForbiddingContext(() => - // Parse the loop body's body. - this.parseStatement(), - ); + using _ = this.withSmartMixTopicForbiddingContext(); + + // Parse the loop body's body. + node.body = this.parseStatement(); + } this.state.labels.pop(); @@ -1159,14 +1159,12 @@ export default abstract class StatementParser extends ExpressionParser { this.scope.enter(ScopeFlag.OTHER); } + // For the smartPipelines plugin: Disable topic references from outer + // contexts within the catch clause's body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the catch clause's body. - clause.body = - // For the smartPipelines plugin: Disable topic references from outer - // contexts within the catch clause's body. - this.withSmartMixTopicForbiddingContext(() => - // Parse the catch clause's body. - this.parseBlock(false, false), - ); + clause.body = this.parseBlock(false, false); this.scope.exit(); node.handler = this.finishNode(clause, "CatchClause"); @@ -1204,15 +1202,13 @@ export default abstract class StatementParser extends ExpressionParser { node.test = this.parseHeaderExpression(); this.state.labels.push(loopLabel); + // For the smartPipelines plugin: + // Disable topic references from outer contexts within the loop body. + // They are permitted in test expressions, outside of the loop body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the loop body. - node.body = - // For the smartPipelines plugin: - // Disable topic references from outer contexts within the loop body. - // They are permitted in test expressions, outside of the loop body. - this.withSmartMixTopicForbiddingContext(() => - // Parse loop body. - this.parseStatement(), - ); + node.body = this.parseStatement(); this.state.labels.pop(); @@ -1229,16 +1225,14 @@ export default abstract class StatementParser extends ExpressionParser { this.next(); node.object = this.parseHeaderExpression(); + // For the smartPipelines plugin: + // Disable topic references from outer contexts within the with statement's body. + // They are permitted in function default-parameter expressions, which are + // part of the outer context, outside of the with statement's body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the statement body. - node.body = - // For the smartPipelines plugin: - // Disable topic references from outer contexts within the with statement's body. - // They are permitted in function default-parameter expressions, which are - // part of the outer context, outside of the with statement's body. - this.withSmartMixTopicForbiddingContext(() => - // Parse the statement body. - this.parseStatement(), - ); + node.body = this.parseStatement(); return this.finishNode(node, "WithStatement"); } @@ -1432,15 +1426,13 @@ export default abstract class StatementParser extends ExpressionParser { node.update = this.match(tt.parenR) ? null : this.parseExpression(); this.expect(tt.parenR); + // For the smartPipelines plugin: Disable topic references from outer + // contexts within the loop body. They are permitted in test expressions, + // outside of the loop body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the loop body. - node.body = - // For the smartPipelines plugin: Disable topic references from outer - // contexts within the loop body. They are permitted in test expressions, - // outside of the loop body. - this.withSmartMixTopicForbiddingContext(() => - // Parse the loop body. - this.parseStatement(), - ); + node.body = this.parseStatement(); this.scope.exit(); this.state.labels.pop(); @@ -1492,15 +1484,13 @@ export default abstract class StatementParser extends ExpressionParser { : this.parseMaybeAssignAllowIn(); this.expect(tt.parenR); + // For the smartPipelines plugin: + // Disable topic references from outer contexts within the loop body. + // They are permitted in test expressions, outside of the loop body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the loop body. - node.body = - // For the smartPipelines plugin: - // Disable topic references from outer contexts within the loop body. - // They are permitted in test expressions, outside of the loop body. - this.withSmartMixTopicForbiddingContext(() => - // Parse loop body. - this.parseStatement(), - ); + node.body = this.parseStatement(); this.scope.exit(); this.state.labels.pop(); @@ -1624,13 +1614,12 @@ export default abstract class StatementParser extends ExpressionParser { // For the smartPipelines plugin: Disable topic references from outer // contexts within the function body. They are permitted in function // default-parameter expressions, outside of the function body. - this.withSmartMixTopicForbiddingContext(() => { - // Parse the function body. - this.parseFunctionBodyAndFinish( - node, - isDeclaration ? "FunctionDeclaration" : "FunctionExpression", - ); - }); + using _ = this.withSmartMixTopicForbiddingContext(); + + this.parseFunctionBodyAndFinish( + node, + isDeclaration ? "FunctionDeclaration" : "FunctionExpression", + ); this.prodParam.exit(); this.scope.exit(); @@ -1748,9 +1737,11 @@ export default abstract class StatementParser extends ExpressionParser { this.expect(tt.braceL); - // For the smartPipelines plugin: Disable topic references from outer - // contexts within the class body. - this.withSmartMixTopicForbiddingContext(() => { + { + // For the smartPipelines plugin: Disable topic references from outer + // contexts within the class body. + using _ = this.withSmartMixTopicForbiddingContext(); + // Parse the contents within the braces. while (!this.match(tt.braceR)) { if (this.eat(tt.semi)) { @@ -1791,7 +1782,7 @@ export default abstract class StatementParser extends ExpressionParser { this.raise(Errors.DecoratorConstructor, member); } } - }); + } this.state.strict = oldStrict; diff --git a/packages/babel-parser/src/tokenizer/index.ts b/packages/babel-parser/src/tokenizer/index.ts index 33a2026a8ddd..0a49d1d2ed5d 100644 --- a/packages/babel-parser/src/tokenizer/index.ts +++ b/packages/babel-parser/src/tokenizer/index.ts @@ -99,6 +99,18 @@ export default abstract class Tokenizer extends CommentsParser { this.isLookahead = false; } + withState(key: K, value: State[K]) { + const { state } = this; + const oldValue = state[key]; + state[key] = value; + return { + // Use Symbol.for("Symbol.dispose") for compat with older platforms + [Symbol.dispose || Symbol.for("Symbol.dispose")]() { + state[key] = oldValue; + }, + }; + } + pushToken(token: Token | N.Comment) { // Pop out invalid tokens trapped by try-catch parsing. // Those parsing branches are mainly created by typescript and flow plugins. diff --git a/packages/babel-parser/src/tokenizer/state.ts b/packages/babel-parser/src/tokenizer/state.ts index 853544c5b16f..420c3843a8b9 100644 --- a/packages/babel-parser/src/tokenizer/state.ts +++ b/packages/babel-parser/src/tokenizer/state.ts @@ -11,18 +11,6 @@ export type DeferredStrictError = | typeof Errors.StrictNumericEscape | typeof Errors.StrictOctalLiteral; -type TopicContextState = { - // When a topic binding has been currently established, - // then this is 1. Otherwise, it is 0. This is forwards compatible - // with a future plugin for multiple lexical topics. - maxNumOfResolvableTopics: number; - // When a topic binding has been currently established, and if that binding - // has been used as a topic reference `#`, then this is 0. Otherwise, it is - // `null`. This is forwards compatible with a future plugin for multiple - // lexical topics. - maxTopicIndex: null | 0; -}; - const enum StateFlags { None = 0, Strict = 1 << 0, @@ -169,11 +157,9 @@ export default class State { } } - // For the Hack-style pipelines plugin - topicContext: TopicContextState = { - maxNumOfResolvableTopics: 0, - maxTopicIndex: null, - }; + // For the Hack-style and Smart-style pipelines plugins. + // When null, topic references are not allowed. + topicReferenceUsage: null | { used: boolean } = null; // For the F#-style pipelines plugin get soloAwait(): boolean { @@ -294,7 +280,9 @@ export default class State { state.potentialArrowAt = this.potentialArrowAt; state.noArrowAt = this.noArrowAt.slice(); state.noArrowParamsConversionAt = this.noArrowParamsConversionAt.slice(); - state.topicContext = this.topicContext; + state.topicReferenceUsage = state.topicReferenceUsage + ? { ...state.topicReferenceUsage } + : null; state.labels = this.labels.slice(); state.commentsLen = this.commentsLen; state.commentStack = this.commentStack.slice(); diff --git a/yarn.lock b/yarn.lock index b314295a2938..d6eb2ef51a9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1450,6 +1450,18 @@ __metadata: languageName: unknown linkType: soft +"@babel/plugin-proposal-explicit-resource-management@npm:8.0.0-alpha.5": + version: 8.0.0-alpha.5 + resolution: "@babel/plugin-proposal-explicit-resource-management@npm:8.0.0-alpha.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^8.0.0-alpha.5" + "@babel/plugin-syntax-explicit-resource-management": "npm:^8.0.0-alpha.5" + peerDependencies: + "@babel/core": ^8.0.0-alpha.5 + checksum: f4ddbf5c363fee2d67cbce6c69b793e8d9e71da84caabce558b5d19cd4df117e1b0a8c1e00cc068fa4557c1363fdcc1b87b1b657e0b2ee3bf6b3a5f6303b22b4 + languageName: node + linkType: hard + "@babel/plugin-proposal-explicit-resource-management@workspace:^, @babel/plugin-proposal-explicit-resource-management@workspace:packages/babel-plugin-proposal-explicit-resource-management": version: 0.0.0-use.local resolution: "@babel/plugin-proposal-explicit-resource-management@workspace:packages/babel-plugin-proposal-explicit-resource-management" @@ -1790,6 +1802,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-explicit-resource-management@npm:^8.0.0-alpha.5": + version: 8.0.0-alpha.5 + resolution: "@babel/plugin-syntax-explicit-resource-management@npm:8.0.0-alpha.5" + dependencies: + "@babel/helper-plugin-utils": "npm:^8.0.0-alpha.5" + peerDependencies: + "@babel/core": ^8.0.0-alpha.5 + checksum: 60414d8fcd69fbf400e621367e34cdf5e48de942664c69551bf87f80502818bd463e32a4ce8f225754a93eecadb3af32a47dae26d1caadcc9f77f5c102c0d1a0 + languageName: node + linkType: hard + "@babel/plugin-syntax-explicit-resource-management@workspace:^, @babel/plugin-syntax-explicit-resource-management@workspace:packages/babel-plugin-syntax-explicit-resource-management": version: 0.0.0-use.local resolution: "@babel/plugin-syntax-explicit-resource-management@workspace:packages/babel-plugin-syntax-explicit-resource-management" @@ -6882,6 +6905,7 @@ __metadata: "@babel/eslint-parser": "workspace:^" "@babel/eslint-plugin-development": "workspace:^" "@babel/eslint-plugin-development-internal": "workspace:^" + "@babel/plugin-proposal-explicit-resource-management": "npm:8.0.0-alpha.5" "@babel/plugin-transform-dynamic-import": "npm:8.0.0-alpha.5" "@babel/plugin-transform-export-namespace-from": "npm:8.0.0-alpha.5" "@babel/plugin-transform-modules-commonjs": "npm:8.0.0-alpha.5"