diff --git a/package-lock.json b/package-lock.json index a478cfba..b6adbe93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.16.1", + "version": "0.17.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-parser", - "version": "0.16.1", + "version": "0.17.0", "license": "MIT", "dependencies": { "grapheme-splitter": "^1.0.4", @@ -892,10 +892,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2330,10 +2331,11 @@ "dev": true }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4167,9 +4169,9 @@ "dev": true }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -5205,9 +5207,9 @@ "dev": true }, "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { "balanced-match": "^1.0.0" diff --git a/package.json b/package.json index 112ff476..0933673b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.16.1", + "version": "0.17.0", "description": "A parser for the Power Query/M formula language.", "author": "Microsoft", "license": "MIT", diff --git a/src/powerquery-parser/parser/parseBehavior.ts b/src/powerquery-parser/parser/parseBehavior.ts new file mode 100644 index 00000000..aedfb6e2 --- /dev/null +++ b/src/powerquery-parser/parser/parseBehavior.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export enum ParseBehavior { + ParseEitherExpressionOrSection = "ParseEitherExpressionOrSection", + ParseExpression = "ParseExpression", + ParseSection = "ParseSection", +} diff --git a/src/powerquery-parser/parser/parseSettings.ts b/src/powerquery-parser/parser/parseSettings.ts index c9ae8311..670e4220 100644 --- a/src/powerquery-parser/parser/parseSettings.ts +++ b/src/powerquery-parser/parser/parseSettings.ts @@ -4,12 +4,14 @@ import { Ast } from "../language"; import { CommonSettings } from "../common"; import { LexerSnapshot } from "../lexer"; +import { ParseBehavior } from "./parseBehavior"; import { Parser } from "./parser"; import { ParseState } from "./parseState"; export interface ParseSettings extends CommonSettings { + readonly parseBehavior: ParseBehavior; readonly parser: Parser; - readonly newParseState: (lexerSnapshot: LexerSnapshot, overrides: Partial | undefined) => ParseState; + readonly newParseState: (lexerSnapshot: LexerSnapshot, overrides?: Partial) => ParseState; readonly parserEntryPoint: | ((state: ParseState, parser: Parser, correlationId: number | undefined) => Promise) | undefined; diff --git a/src/powerquery-parser/parser/parseState/parseState.ts b/src/powerquery-parser/parser/parseState/parseState.ts index 7edb5b33..e04e302f 100644 --- a/src/powerquery-parser/parser/parseState/parseState.ts +++ b/src/powerquery-parser/parser/parseState/parseState.ts @@ -4,6 +4,7 @@ import { Disambiguation } from "../disambiguation"; import { ICancellationToken } from "../../common"; import { LexerSnapshot } from "../../lexer"; +import { ParseBehavior } from "../parseBehavior"; import { ParseContext } from ".."; import { Token } from "../../language"; import { TraceManager } from "../../common/trace"; @@ -13,6 +14,7 @@ export interface ParseState { readonly disambiguationBehavior: Disambiguation.DismabiguationBehavior; readonly lexerSnapshot: LexerSnapshot; readonly locale: string; + readonly parseBehavior: ParseBehavior; readonly traceManager: TraceManager; contextState: ParseContext.State; currentContextNode: ParseContext.TNode | undefined; diff --git a/src/powerquery-parser/parser/parseState/parseStateUtils.ts b/src/powerquery-parser/parser/parseState/parseStateUtils.ts index e4c3e5eb..74d60886 100644 --- a/src/powerquery-parser/parser/parseState/parseStateUtils.ts +++ b/src/powerquery-parser/parser/parseState/parseStateUtils.ts @@ -8,10 +8,11 @@ import { NoOpTraceManagerInstance, Trace } from "../../common/trace"; import { DefaultLocale } from "../../localization"; import { Disambiguation } from "../disambiguation"; import { LexerSnapshot } from "../../lexer"; +import { ParseBehavior } from "../parseBehavior"; import { ParseState } from "./parseState"; import { SequenceKind } from "../error"; -export function newState(lexerSnapshot: LexerSnapshot, overrides: Partial | undefined): ParseState { +export function newState(lexerSnapshot: LexerSnapshot, overrides?: Partial): ParseState { const tokenIndex: number = overrides?.tokenIndex ?? 0; const currentToken: Token.Token | undefined = lexerSnapshot.tokens[tokenIndex]; const currentTokenKind: Token.TokenKind | undefined = currentToken?.kind; @@ -30,6 +31,7 @@ export function newState(lexerSnapshot: LexerSnapshot, overrides: Partial Ast.IdentifierExpression; - // 12.2.1 Documents - readonly readDocument: ( - state: ParseState, - parser: Parser, - correlationId: number | undefined, - ) => Promise; - // 12.2.2 Section Documents readonly readSectionDocument: ( state: ParseState, diff --git a/src/powerquery-parser/parser/parser/parserUtils.ts b/src/powerquery-parser/parser/parser/parserUtils.ts index 7ac31d81..d581043d 100644 --- a/src/powerquery-parser/parser/parser/parserUtils.ts +++ b/src/powerquery-parser/parser/parser/parserUtils.ts @@ -1,14 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, CommonError, ResultUtils } from "../../common"; +import { Assert, CommonError, Result, ResultUtils } from "../../common"; import { NodeIdMap, NodeIdMapUtils } from "../nodeIdMap"; import { ParseContext, ParseContextUtils } from "../context"; import { ParseError, ParseSettings } from ".."; -import { Parser, ParseStateCheckpoint, TriedParse } from "./parser"; +import { ParseOk, Parser, ParseStateCheckpoint, TriedParse } from "./parser"; import { ParseState, ParseStateUtils } from "../parseState"; import { Ast } from "../../language"; import { LexerSnapshot } from "../../lexer"; +import { ParseBehavior } from "../parseBehavior"; import { Trace } from "../../common/trace"; export async function tryParse(parseSettings: ParseSettings, lexerSnapshot: LexerSnapshot): Promise { @@ -23,101 +24,13 @@ export async function tryParse(parseSettings: ParseSettings, lexerSnapshot: Lexe initialCorrelationId: trace.id, }; - const parserEntryPoint: - | ((state: ParseState, parser: Parser, correlationId: number | undefined) => Promise) - | undefined = updatedSettings?.parserEntryPoint; + const result: TriedParse = updatedSettings.parserEntryPoint + ? await tryParseEntryPoint(updatedSettings.parserEntryPoint, updatedSettings, lexerSnapshot) + : await tryParseDocument(updatedSettings, lexerSnapshot); - if (parserEntryPoint === undefined) { - return await tryParseDocument(updatedSettings, lexerSnapshot); - } - - const parseState: ParseState = updatedSettings.newParseState(lexerSnapshot, defaultOverrides(updatedSettings)); - - try { - const root: Ast.TNode = await parserEntryPoint(parseState, updatedSettings.parser, trace.id); - ParseStateUtils.assertIsDoneParsing(parseState); - - return ResultUtils.ok({ - lexerSnapshot, - root, - state: parseState, - }); - } catch (caught: unknown) { - Assert.isInstanceofError(caught); - CommonError.throwIfCancellationError(caught); + trace.exit(); - return ResultUtils.error(ensureParseError(parseState, caught, updatedSettings.locale)); - } -} - -// Attempts to parse the document both as an expression and section document. -// Whichever attempt consumed the most tokens is the one returned. Ties go to expression documents. -export async function tryParseDocument( - parseSettings: ParseSettings, - lexerSnapshot: LexerSnapshot, -): Promise { - const trace: Trace = parseSettings.traceManager.entry( - ParserUtilsTraceConstant.ParserUtils, - tryParseDocument.name, - parseSettings.initialCorrelationId, - ); - - let root: Ast.TNode; - - const expressionDocumentState: ParseState = parseSettings.newParseState( - lexerSnapshot, - defaultOverrides(parseSettings), - ); - - try { - root = await parseSettings.parser.readExpression(expressionDocumentState, parseSettings.parser, trace.id); - ParseStateUtils.assertIsDoneParsing(expressionDocumentState); - trace.exit(); - - return ResultUtils.ok({ - lexerSnapshot, - root, - state: expressionDocumentState, - }); - } catch (expressionDocumentError: unknown) { - Assert.isInstanceofError(expressionDocumentError); - CommonError.throwIfCancellationError(expressionDocumentError); - - const sectionDocumentState: ParseState = parseSettings.newParseState( - lexerSnapshot, - defaultOverrides(parseSettings), - ); - - try { - root = await parseSettings.parser.readSectionDocument(sectionDocumentState, parseSettings.parser, trace.id); - ParseStateUtils.assertIsDoneParsing(sectionDocumentState); - trace.exit(); - - return ResultUtils.ok({ - lexerSnapshot, - root, - state: sectionDocumentState, - }); - } catch (sectionDocumentError: unknown) { - Assert.isInstanceofError(sectionDocumentError); - CommonError.throwIfCancellationError(expressionDocumentError); - - let betterParsedState: ParseState; - let betterParsedError: Error; - - if (expressionDocumentState.tokenIndex >= sectionDocumentState.tokenIndex) { - betterParsedState = expressionDocumentState; - betterParsedError = expressionDocumentError; - } else { - betterParsedState = sectionDocumentState; - betterParsedError = sectionDocumentError; - } - - trace.exit(); - - return ResultUtils.error(ensureParseError(betterParsedState, betterParsedError, parseSettings.locale)); - } - } + return result; } // If you have a custom parser + parser state, @@ -208,5 +121,199 @@ function defaultOverrides(parseSettings: ParseSettings): Partial { locale: parseSettings.locale, cancellationToken: parseSettings.cancellationToken, traceManager: parseSettings.traceManager, + parseBehavior: parseSettings.parseBehavior, }; } + +async function tryParseEntryPoint( + parserEntryPoint: (state: ParseState, parser: Parser, correlationId: number | undefined) => Promise, + parseSettings: ParseSettings, + lexerSnapshot: LexerSnapshot, +): Promise { + const trace: Trace = parseSettings.traceManager.entry( + ParserUtilsTraceConstant.ParserUtils, + tryParseEntryPoint.name, + parseSettings.initialCorrelationId, + ); + + const parseState: ParseState = parseSettings.newParseState(lexerSnapshot, defaultOverrides(parseSettings)); + + try { + const root: Ast.TNode = await parserEntryPoint(parseState, parseSettings.parser, trace.id); + ParseStateUtils.assertIsDoneParsing(parseState); + + trace.exit(); + + return ResultUtils.ok({ + lexerSnapshot, + root, + state: parseState, + }); + } catch (caught: unknown) { + Assert.isInstanceofError(caught); + CommonError.throwIfCancellationError(caught); + + const result: TriedParse = ResultUtils.error(ensureParseError(parseState, caught, parseSettings.locale)); + + trace.exit(); + + return result; + } +} + +// Attempts to parse the document both as an expression and section document. +// Whichever attempt consumed the most tokens is the one returned. Ties go to expression documents. +async function tryParseDocument(parseSettings: ParseSettings, lexerSnapshot: LexerSnapshot): Promise { + switch (parseSettings.parseBehavior) { + case ParseBehavior.ParseEitherExpressionOrSection: + return await tryParseExpressionDocumentOrSectionDocument(parseSettings, lexerSnapshot); + + case ParseBehavior.ParseExpression: { + const expressionParseResult: InternalTriedParse = await tryParseExpressionDocument( + parseSettings, + lexerSnapshot, + ); + + return ResultUtils.isOk(expressionParseResult) + ? expressionParseResult + : ResultUtils.error(expressionParseResult.error.innerError); + } + + case ParseBehavior.ParseSection: { + const sectionParseResult: InternalTriedParse = await tryParseSectionDocument(parseSettings, lexerSnapshot); + + return ResultUtils.isOk(sectionParseResult) + ? sectionParseResult + : ResultUtils.error(sectionParseResult.error.innerError); + } + + default: + Assert.isNever(parseSettings.parseBehavior); + } +} + +async function tryParseExpressionDocumentOrSectionDocument( + parseSettings: ParseSettings, + lexerSnapshot: LexerSnapshot, +): Promise { + const trace: Trace = parseSettings.traceManager.entry( + ParserUtilsTraceConstant.ParserUtils, + tryParseExpressionDocumentOrSectionDocument.name, + parseSettings.initialCorrelationId, + ); + + const parseExpressionResult: InternalTriedParse = await tryParseExpressionDocument(parseSettings, lexerSnapshot); + + if (ResultUtils.isOk(parseExpressionResult)) { + trace.exit(); + + return parseExpressionResult; + } + + // If the expression parse failed, try parsing as a section document. + const parseSectionResult: InternalTriedParse = await tryParseSectionDocument(parseSettings, lexerSnapshot); + + if (ResultUtils.isOk(parseSectionResult)) { + trace.exit(); + + return parseSectionResult; + } + + // If both parse attempts fail then return the instance with the most tokens consumed. + // On ties fallback to the expression parse attempt. + const errorResult: TriedParse = ResultUtils.error( + parseExpressionResult.error.tokensConsumed >= parseSectionResult.error.tokensConsumed + ? parseExpressionResult.error.innerError + : parseSectionResult.error.innerError, + ); + + trace.exit(); + + return errorResult; +} + +async function tryParseExpressionDocument( + parseSettings: ParseSettings, + lexerSnapshot: LexerSnapshot, +): Promise { + const trace: Trace = parseSettings.traceManager.entry( + ParserUtilsTraceConstant.ParserUtils, + tryParseExpressionDocument.name, + parseSettings.initialCorrelationId, + ); + + const result: InternalTriedParse = await tryParseHelper( + parseSettings, + lexerSnapshot, + parseSettings.parser.readExpression, + trace.id, + ); + + trace.exit(); + + return result; +} + +async function tryParseSectionDocument( + parseSettings: ParseSettings, + lexerSnapshot: LexerSnapshot, +): Promise { + const trace: Trace = parseSettings.traceManager.entry( + ParserUtilsTraceConstant.ParserUtils, + tryParseSectionDocument.name, + parseSettings.initialCorrelationId, + ); + + const result: InternalTriedParse = await tryParseHelper( + parseSettings, + lexerSnapshot, + parseSettings.parser.readSectionDocument, + trace.id, + ); + + trace.exit(); + + return result; +} + +async function tryParseHelper( + parseSettings: ParseSettings, + lexerSnapshot: LexerSnapshot, + parserFunction: (state: ParseState, parser: Parser, correlationId: number | undefined) => Promise, + correlationId: number | undefined, +): Promise { + const parseState: ParseState = parseSettings.newParseState(lexerSnapshot, defaultOverrides(parseSettings)); + + try { + const root: T = await parserFunction(parseState, parseSettings.parser, correlationId); + + ParseStateUtils.assertIsDoneParsing(parseState); + + return ResultUtils.ok({ + lexerSnapshot, + root, + state: parseState, + }); + } catch (error: unknown) { + Assert.isInstanceofError(error); + CommonError.throwIfCancellationError(error); + + const result: InternalTriedParse = ResultUtils.error({ + innerError: ensureParseError(parseState, error, parseSettings.locale), + tokensConsumed: parseState.tokenIndex, + }); + + return result; + } +} + +// Note: Internal type +// Used specifically for comparing parse attempts when both an expression and section document are attempted. +// Not a general extension of TriedParse; do not use outside this context. +// Adds `tokensConsumed` to the TriedParse type to help determine which parse attempt should be returned. +type InternalTriedParse = Result; + +interface InternalTriedParseError { + readonly innerError: ParseError.TParseError; + readonly tokensConsumed: number; +} diff --git a/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts b/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts index a8daf630..6b538de6 100644 --- a/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts +++ b/src/powerquery-parser/parser/parsers/combinatorialParserV2/combinatorialParserV2.ts @@ -31,8 +31,6 @@ export const CombinatorialParserV2: Parser = { readGeneralizedIdentifier: NaiveParseSteps.readGeneralizedIdentifier, readKeyword: NaiveParseSteps.readKeyword, - readDocument: NaiveParseSteps.readDocument, - readSectionDocument: NaiveParseSteps.readSectionDocument, readSectionMembers: NaiveParseSteps.readSectionMembers, readSectionMember: NaiveParseSteps.readSectionMember, diff --git a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts index f6833858..1017eb54 100644 --- a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts +++ b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts @@ -4,7 +4,7 @@ import { Assert, CommonError, Result, ResultUtils } from "../../common"; import { Ast, AstUtils, Constant, ConstantUtils, IdentifierUtils, Token } from "../../language"; import { Disambiguation, DisambiguationUtils } from "../disambiguation"; -import { NaiveParseSteps, ParseContext, ParseContextUtils, ParseError } from ".."; +import { NaiveParseSteps, ParseError } from ".."; import { Parser, ParseStateCheckpoint } from "../parser"; import { ParseState, ParseStateUtils } from "../parseState"; import { Trace, TraceConstant } from "../../common/trace"; @@ -197,83 +197,6 @@ export function readKeyword( return identifierExpression; } -// -------------------------------------- -// ---------- 12.2.1 Documents ---------- -// -------------------------------------- - -export async function readDocument( - state: ParseState, - parser: Parser, - correlationId: number | undefined, -): Promise { - const trace: Trace = state.traceManager.entry(NaiveTraceConstant.Parse, readDocument.name, correlationId, { - [NaiveTraceConstant.TokenIndex]: state.tokenIndex, - }); - - state.cancellationToken?.throwIfCancelled(); - - let document: Ast.TDocument; - - // Try parsing as an Expression document first. - // If Expression document fails (including UnusedTokensRemainError) then try parsing a SectionDocument. - // If both fail then return the error which parsed more tokens. - try { - document = await parser.readExpression(state, parser, trace.id); - ParseStateUtils.assertIsDoneParsing(state); - } catch (expressionError: unknown) { - Assert.isInstanceofError(expressionError); - CommonError.throwIfCancellationError(expressionError); - - // Fast backup deletes context state, but we want to preserve it for the case - // where both parsing an expression and section document error out. - const expressionCheckpoint: ParseStateCheckpoint = await parser.checkpoint(state); - const expressionErrorContextState: ParseContext.State = state.contextState; - - // Reset the parser's state. - state.tokenIndex = 0; - state.contextState = ParseContextUtils.newState(); - state.currentContextNode = undefined; - - if (state.lexerSnapshot.tokens.length) { - state.currentToken = state.lexerSnapshot.tokens[0]; - state.currentTokenKind = state.currentToken?.kind; - } - - try { - document = await readSectionDocument(state, parser, trace.id); - ParseStateUtils.assertIsDoneParsing(state); - } catch (sectionError: unknown) { - Assert.isInstanceofError(sectionError); - CommonError.throwIfCancellationError(sectionError); - - let triedError: Error; - - if (expressionCheckpoint.tokenIndex > /* sectionErrorState */ state.tokenIndex) { - triedError = expressionError; - await parser.restoreCheckpoint(state, expressionCheckpoint); - // eslint-disable-next-line require-atomic-updates - state.contextState = expressionErrorContextState; - } else { - triedError = sectionError; - } - - trace.exit({ - [NaiveTraceConstant.TokenIndex]: state.tokenIndex, - [TraceConstant.IsThrowing]: true, - }); - - throw triedError; - } - } - - trace.exit({ - [NaiveTraceConstant.TokenIndex]: state.tokenIndex, - [TraceConstant.IsThrowing]: false, - }); - - return document; -} - // ---------------------------------------------- // ---------- 12.2.2 Section Documents ---------- // ---------------------------------------------- diff --git a/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts b/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts index 6d53f512..923ea84a 100644 --- a/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts +++ b/src/powerquery-parser/parser/parsers/recursiveDescentParser.ts @@ -15,8 +15,6 @@ export const RecursiveDescentParser: Parser = { readGeneralizedIdentifier: NaiveParseSteps.readGeneralizedIdentifier, readKeyword: NaiveParseSteps.readKeyword, - readDocument: NaiveParseSteps.readDocument, - readSectionDocument: NaiveParseSteps.readSectionDocument, readSectionMembers: NaiveParseSteps.readSectionMembers, readSectionMember: NaiveParseSteps.readSectionMember, diff --git a/src/powerquery-parser/settings.ts b/src/powerquery-parser/settings.ts index 9980b6a7..be7c0bcf 100644 --- a/src/powerquery-parser/settings.ts +++ b/src/powerquery-parser/settings.ts @@ -5,16 +5,18 @@ import { CombinatorialParserV2, ParseSettings, ParseState, ParseStateUtils } fro import { LexerSnapshot, LexSettings } from "./lexer"; import { DefaultLocale } from "./localization"; import { NoOpTraceManagerInstance } from "./common/trace"; +import { ParseBehavior } from "./parser/parseBehavior"; export type Settings = LexSettings & ParseSettings; export const DefaultSettings: Settings = { - newParseState: (lexerSnapshot: LexerSnapshot, overrides: Partial | undefined) => + newParseState: (lexerSnapshot: LexerSnapshot, overrides?: Partial) => ParseStateUtils.newState(lexerSnapshot, overrides), locale: DefaultLocale, cancellationToken: undefined, initialCorrelationId: undefined, parserEntryPoint: undefined, + parseBehavior: ParseBehavior.ParseEitherExpressionOrSection, parser: CombinatorialParserV2, traceManager: NoOpTraceManagerInstance, }; diff --git a/src/test/libraryTest/parser/parseBehavior.test.ts b/src/test/libraryTest/parser/parseBehavior.test.ts new file mode 100644 index 00000000..d56ce742 --- /dev/null +++ b/src/test/libraryTest/parser/parseBehavior.test.ts @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "mocha"; +import { expect } from "chai"; + +import * as ParserTestUtils from "./parserTestUtils"; +import { Assert, DefaultSettings, Task, TaskUtils } from "../../../powerquery-parser"; +import { NodeKind } from "../../../powerquery-parser/language/ast/ast"; +import { ParseBehavior } from "../../../powerquery-parser/parser/parseBehavior"; +import { ParseError } from "../../../powerquery-parser/parser"; + +describe("ParseBehavior", () => { + async function runParseBehaviorTest(params: { + readonly parseBehavior: ParseBehavior; + readonly text: string; + readonly expectedAbridgedNodes: ReadonlyArray; + readonly expectedStatus: "ExpectedAnyTokenKindError" | "ExpectedTokenKindError" | "ParseStageOk"; + }): Promise { + const result: Task.ParseTaskOk | Task.ParseTaskParseError = await ParserTestUtils.runAbridgedNodeTest( + params.text, + params.expectedAbridgedNodes, + { + astOnly: true, + settings: { + ...DefaultSettings, + parseBehavior: params.parseBehavior, + }, + }, + ); + + switch (params.expectedStatus) { + case "ExpectedAnyTokenKindError": + TaskUtils.assertIsParseStageError(result); + expect(result.error.innerError).to.be.instanceOf(ParseError.ExpectedAnyTokenKindError); + break; + + case "ExpectedTokenKindError": + TaskUtils.assertIsParseStageError(result); + expect(result.error.innerError).to.be.instanceOf(ParseError.ExpectedTokenKindError); + break; + + case "ParseStageOk": + TaskUtils.assertIsParseStageOk(result); + break; + + default: + Assert.isNever(params.expectedStatus); + } + + return result; + } + + it(`1 // with ParseEitherExpressionOrSection`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseEitherExpressionOrSection, + text: `1`, + expectedAbridgedNodes: [[NodeKind.LiteralExpression, undefined]], + expectedStatus: "ParseStageOk", + }); + }); + + it(`1 // with ParseExpression`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseExpression, + text: `1`, + expectedAbridgedNodes: [[NodeKind.LiteralExpression, undefined]], + expectedStatus: "ParseStageOk", + }); + }); + + it(`1 // with ParseSection`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseSection, + text: `1`, + expectedAbridgedNodes: [], + expectedStatus: "ExpectedTokenKindError", + }); + }); + + it(`section Foo; shared Bar = 1; // with ParseEitherExpressionOrSection`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseEitherExpressionOrSection, + text: `section Foo; shared Bar = 1;`, + expectedAbridgedNodes: [ + [NodeKind.Section, undefined], + [NodeKind.Constant, 1], + [NodeKind.Identifier, 2], + [NodeKind.Constant, 3], + [NodeKind.ArrayWrapper, 4], + [NodeKind.SectionMember, 0], + [NodeKind.Constant, 1], + [NodeKind.IdentifierPairedExpression, 2], + [NodeKind.Identifier, 0], + [NodeKind.Constant, 1], + [NodeKind.LiteralExpression, 2], + [NodeKind.Constant, 3], + ], + expectedStatus: "ParseStageOk", + }); + }); + + it(`section Foo; shared Bar = 1; // with ParseExpression`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseExpression, + text: `section Foo; shared Bar = 1;`, + expectedAbridgedNodes: [], + expectedStatus: "ExpectedAnyTokenKindError", + }); + }); + + it(`section Foo; shared Bar = 1; // with ParseSection`, async () => { + await runParseBehaviorTest({ + parseBehavior: ParseBehavior.ParseSection, + text: `section Foo; shared Bar = 1;`, + expectedAbridgedNodes: [ + [NodeKind.Section, undefined], + [NodeKind.Constant, 1], + [NodeKind.Identifier, 2], + [NodeKind.Constant, 3], + [NodeKind.ArrayWrapper, 4], + [NodeKind.SectionMember, 0], + [NodeKind.Constant, 1], + [NodeKind.IdentifierPairedExpression, 2], + [NodeKind.Identifier, 0], + [NodeKind.Constant, 1], + [NodeKind.LiteralExpression, 2], + [NodeKind.Constant, 3], + ], + expectedStatus: "ParseStageOk", + }); + }); +}); diff --git a/src/test/libraryTest/parser/parseSimple.test.ts b/src/test/libraryTest/parser/parseSimple.test.ts index 6f6c64ab..4a383764 100644 --- a/src/test/libraryTest/parser/parseSimple.test.ts +++ b/src/test/libraryTest/parser/parseSimple.test.ts @@ -2,184 +2,14 @@ // Licensed under the MIT license. import "mocha"; -import { expect } from "chai"; -import { Assert, DefaultLocale, DefaultSettings, ResultUtils, Task, TaskUtils, Traverse } from "../../.."; +import * as ParserTestUtils from "./parserTestUtils"; import { Ast, Constant } from "../../../powerquery-parser/language"; -import { NodeIdMap, TXorNode, XorNodeUtils } from "../../../powerquery-parser/parser"; -import { AssertTestUtils } from "../../testUtils"; -import { NoOpTraceManagerInstance } from "../../../powerquery-parser/common/trace"; - -type AbridgedNode = [Ast.NodeKind, number | undefined]; - -type CollectAbridgeNodeState = Traverse.ITraversalState; - -interface NthNodeOfKindState extends Traverse.ITraversalState { - readonly nodeKind: Ast.NodeKind; - readonly nthRequired: number; - nthCounter: number; -} - -async function collectAbridgeNodeFromXor( - nodeIdMapCollection: NodeIdMap.Collection, - root: TXorNode, -): Promise> { - const state: CollectAbridgeNodeState = { - locale: DefaultLocale, - result: [], - cancellationToken: undefined, - initialCorrelationId: undefined, - traceManager: NoOpTraceManagerInstance, - }; - - const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseXor< - CollectAbridgeNodeState, - AbridgedNode[] - >( - state, - nodeIdMapCollection, - root, - Traverse.VisitNodeStrategy.BreadthFirst, - collectAbridgeXorNodeVisit, - Traverse.assertGetAllXorChildren, - undefined, - ); - - ResultUtils.assertIsOk(triedTraverse); - - return triedTraverse.value; -} - -async function assertGetNthNodeOfKind( - text: string, - nodeKind: Ast.NodeKind, - nthRequired: number, -): Promise { - const parseTaskOk: Task.ParseTaskOk = await AssertTestUtils.assertGetLexParseOk(DefaultSettings, text); - - const state: NthNodeOfKindState = { - locale: DefaultLocale, - result: undefined, - nodeKind, - nthCounter: 0, - nthRequired, - cancellationToken: undefined, - initialCorrelationId: undefined, - traceManager: NoOpTraceManagerInstance, - }; - - const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseAst< - NthNodeOfKindState, - Ast.TNode | undefined - >( - state, - parseTaskOk.nodeIdMapCollection, - parseTaskOk.ast, - Traverse.VisitNodeStrategy.BreadthFirst, - nthNodeVisit, - Traverse.assertGetAllAstChildren, - nthNodeEarlyExit, - ); - - ResultUtils.assertIsOk(triedTraverse); - - return Assert.asDefined(triedTraverse.value) as N; -} - -// eslint-disable-next-line require-await -async function collectAbridgeXorNodeVisit(state: CollectAbridgeNodeState, xorNode: TXorNode): Promise { - state.result.push([xorNode.node.kind, xorNode.node.attributeIndex]); -} - -// eslint-disable-next-line require-await -async function nthNodeVisit(state: NthNodeOfKindState, node: Ast.TNode): Promise { - if (node.kind === state.nodeKind) { - state.nthCounter += 1; - - if (state.nthCounter === state.nthRequired) { - state.result = node; - } - } -} - -// eslint-disable-next-line require-await -async function nthNodeEarlyExit(state: NthNodeOfKindState, _: Ast.TNode): Promise { - return state.nthCounter === state.nthRequired; -} - -function validateNodeIdMapCollection(nodeIdMapCollection: NodeIdMap.Collection, root: TXorNode): void { - const astNodeIds: Set = new Set(nodeIdMapCollection.astNodeById.keys()); - const contextNodeIds: Set = new Set(nodeIdMapCollection.contextNodeById.keys()); - const allNodeIds: Set = new Set([...astNodeIds].concat([...contextNodeIds])); - - expect(nodeIdMapCollection.parentIdById).to.not.have.key(root.node.id.toString()); - - expect(nodeIdMapCollection.parentIdById.size).to.equal( - allNodeIds.size - 1, - "parentIdById should have one less entry than allNodeIds", - ); - - expect(astNodeIds.size + contextNodeIds.size).to.equal( - allNodeIds.size, - "allNodeIds should be a union of astNodeIds and contextNodeIds", - ); - - for (const [childId, parentId] of nodeIdMapCollection.parentIdById.entries()) { - expect(allNodeIds).to.include(childId, "keys for parentIdById should be in allNodeIds"); - expect(allNodeIds).to.include(parentId, "values for parentIdById should be in allNodeIds"); - } - - for (const [parentId, childrenIds] of nodeIdMapCollection.childIdsById.entries()) { - expect(allNodeIds).to.include(parentId, "keys for childIdsById should be in allNodeIds"); - - for (const childId of childrenIds) { - expect(allNodeIds).to.include(childId, "childIds should be in allNodeIds"); - - if (astNodeIds.has(parentId)) { - expect(astNodeIds).to.include(childId, "if a parent is an astNode then so should be its children"); - } - } - } -} describe("Parser.AbridgedNode", () => { - async function runAbridgedNodeTest(text: string, expected: ReadonlyArray): Promise { - const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text); - - let root: TXorNode; - let nodeIdMapCollection: NodeIdMap.Collection; - - if (TaskUtils.isParseStageOk(triedLexParse)) { - root = XorNodeUtils.boxAst(triedLexParse.ast); - nodeIdMapCollection = triedLexParse.nodeIdMapCollection; - } else if (TaskUtils.isParseStageParseError(triedLexParse)) { - root = XorNodeUtils.boxContext(Assert.asDefined(triedLexParse.parseState.contextState.root)); - nodeIdMapCollection = triedLexParse.nodeIdMapCollection; - } else { - throw new Error(`expected isParseStageOk/isParseStageParseError`); - } - - validateNodeIdMapCollection(nodeIdMapCollection, root); - - const actual: ReadonlyArray = await collectAbridgeNodeFromXor(nodeIdMapCollection, root); - expect(actual).to.deep.equal(expected); - } - - async function runAbridgedNodeAndOperatorTest( - text: string, - constant: Constant.TConstant, - expected: ReadonlyArray, - ): Promise { - await runAbridgedNodeTest(text, expected); - - const operatorNode: Ast.TConstant = await assertGetNthNodeOfKind(text, Ast.NodeKind.Constant, 1); - - expect(operatorNode.constantKind).to.equal(constant); - } - describe(`${Ast.NodeKind.ArithmeticExpression}`, () => { it(`1 &`, async () => { - await runAbridgedNodeTest(`1 &`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 &`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.ArithmeticExpression, 1], @@ -189,7 +19,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 *`, async () => { - await runAbridgedNodeTest(`1 *`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 *`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.ArithmeticExpression, 1], @@ -199,7 +29,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 /`, async () => { - await runAbridgedNodeTest(`1 /`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 /`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.ArithmeticExpression, 1], @@ -209,7 +39,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 +`, async () => { - await runAbridgedNodeTest(`1 +`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 +`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.ArithmeticExpression, 1], @@ -219,7 +49,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 -`, async () => { - await runAbridgedNodeTest(`1 -`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 -`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.ArithmeticExpression, 1], @@ -229,7 +59,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 & 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 & 2`, Constant.ArithmeticOperator.And, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 & 2`, Constant.ArithmeticOperator.And, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -238,7 +68,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 * 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 * 2`, Constant.ArithmeticOperator.Multiplication, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 * 2`, Constant.ArithmeticOperator.Multiplication, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -247,7 +77,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 / 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 / 2`, Constant.ArithmeticOperator.Division, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 / 2`, Constant.ArithmeticOperator.Division, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -256,7 +86,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 + 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 + 2`, Constant.ArithmeticOperator.Addition, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 + 2`, Constant.ArithmeticOperator.Addition, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -265,7 +95,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 - 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 - 2`, Constant.ArithmeticOperator.Subtraction, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 - 2`, Constant.ArithmeticOperator.Subtraction, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -274,7 +104,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 + 2 + 3 + 4`, async () => { - await runAbridgedNodeTest(`1 + 2 + 3 + 4`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 + 2 + 3 + 4`, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.ArithmeticExpression, 0], [Ast.NodeKind.ArithmeticExpression, 0], @@ -291,7 +121,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.AsExpression}`, () => { it(`1 as`, async () => { - await runAbridgedNodeTest(`1 as`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 as`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.AsExpression, 1], @@ -301,7 +131,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 as number`, async () => { - await runAbridgedNodeTest(`1 as number`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 as number`, [ [Ast.NodeKind.AsExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -310,7 +140,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 as number as logical`, async () => { - await runAbridgedNodeTest(`1 as number as logical`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 as number as logical`, [ [Ast.NodeKind.AsExpression, undefined], [Ast.NodeKind.AsExpression, 0], [Ast.NodeKind.LiteralExpression, 0], @@ -322,7 +152,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type function (x as number) as number`, async () => { - await runAbridgedNodeTest(`type function (x as number) as number`, [ + await ParserTestUtils.runAbridgedNodeTest(`type function (x as number) as number`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.FunctionType, 1], @@ -349,7 +179,7 @@ describe("Parser.AbridgedNode", () => { // Ast.Ast.NodeKind.Csv covered by many it(`${Ast.NodeKind.EachExpression}`, async () => { - await runAbridgedNodeTest(`each 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`each 1`, [ [Ast.NodeKind.EachExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -358,7 +188,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.EqualityExpression}`, () => { it(`1 = 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 = 2`, Constant.EqualityOperator.EqualTo, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 = 2`, Constant.EqualityOperator.EqualTo, [ [Ast.NodeKind.EqualityExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -367,7 +197,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 <> 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 <> 2`, Constant.EqualityOperator.NotEqualTo, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 <> 2`, Constant.EqualityOperator.NotEqualTo, [ [Ast.NodeKind.EqualityExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -378,7 +208,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.ErrorHandlingExpression}`, () => { it(`try 1`, async () => { - await runAbridgedNodeTest(`try 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`try 1`, [ [Ast.NodeKind.ErrorHandlingExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -386,7 +216,7 @@ describe("Parser.AbridgedNode", () => { }); it(`try 1 otherwise 2`, async () => { - await runAbridgedNodeTest(`try 1 otherwise 2`, [ + await ParserTestUtils.runAbridgedNodeTest(`try 1 otherwise 2`, [ [Ast.NodeKind.ErrorHandlingExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -397,7 +227,7 @@ describe("Parser.AbridgedNode", () => { }); it(`try 1 catch () => 1`, async () => { - await runAbridgedNodeTest(`try 1 catch () => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`try 1 catch () => 1`, [ [Ast.NodeKind.ErrorHandlingExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -414,7 +244,7 @@ describe("Parser.AbridgedNode", () => { }); it(`try 1 catch (x) => 1`, async () => { - await runAbridgedNodeTest(`try 1 catch (x) => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`try 1 catch (x) => 1`, [ [Ast.NodeKind.ErrorHandlingExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -435,7 +265,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.ErrorRaisingExpression}`, async () => { - await runAbridgedNodeTest(`error 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`error 1`, [ [Ast.NodeKind.ErrorRaisingExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -444,7 +274,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FieldProjection}`, () => { it(`x[[y]]`, async () => { - await runAbridgedNodeTest(`x[[y]]`, [ + await ParserTestUtils.runAbridgedNodeTest(`x[[y]]`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -462,7 +292,7 @@ describe("Parser.AbridgedNode", () => { }); it(`x[[y], [z]]`, async () => { - await runAbridgedNodeTest(`x[[y], [z]]`, [ + await ParserTestUtils.runAbridgedNodeTest(`x[[y], [z]]`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -486,7 +316,7 @@ describe("Parser.AbridgedNode", () => { }); it(`x[[y]]?`, async () => { - await runAbridgedNodeTest(`x[[y]]?`, [ + await ParserTestUtils.runAbridgedNodeTest(`x[[y]]?`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -507,7 +337,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FieldSelector}`, () => { it(`[x]`, async () => { - await runAbridgedNodeTest(`[x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[x]`, [ [Ast.NodeKind.FieldSelector, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.GeneralizedIdentifier, 1], @@ -516,7 +346,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[x]?`, async () => { - await runAbridgedNodeTest(`[x]?`, [ + await ParserTestUtils.runAbridgedNodeTest(`[x]?`, [ [Ast.NodeKind.FieldSelector, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.GeneralizedIdentifier, 1], @@ -528,7 +358,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FieldSpecification}`, () => { it(`type [x]`, async () => { - await runAbridgedNodeTest(`type [x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -543,7 +373,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type [optional x]`, async () => { - await runAbridgedNodeTest(`type [optional x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [optional x]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -559,7 +389,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type [x = number]`, async () => { - await runAbridgedNodeTest(`type [x = number]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x = number]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -579,7 +409,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FieldSpecificationList}`, () => { it(`type []`, async () => { - await runAbridgedNodeTest(`type []`, [ + await ParserTestUtils.runAbridgedNodeTest(`type []`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -591,7 +421,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type table []`, async () => { - await runAbridgedNodeTest(`type table []`, [ + await ParserTestUtils.runAbridgedNodeTest(`type table []`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.TableType, 1], @@ -604,7 +434,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.FieldSpecificationList}`, async () => { - await runAbridgedNodeTest(`type [x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -619,7 +449,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type [x, ...]`, async () => { - await runAbridgedNodeTest(`type [x, ...]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x, ...]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -640,7 +470,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FunctionExpression}`, () => { it(`() => 1`, async () => { - await runAbridgedNodeTest(`() => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`() => 1`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -652,7 +482,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x) => 1`, async () => { - await runAbridgedNodeTest(`(x) => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x) => 1`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -667,7 +497,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x, y, z) => 1`, async () => { - await runAbridgedNodeTest(`(x, y, z) => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x, y, z) => 1`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -690,7 +520,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(optional x) => 1`, async () => { - await runAbridgedNodeTest(`(optional x) => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`(optional x) => 1`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -706,7 +536,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x as nullable text) => 1`, async () => { - await runAbridgedNodeTest(`(x as nullable text) => 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x as nullable text) => 1`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -726,7 +556,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x) as number => x`, async () => { - await runAbridgedNodeTest(`(x) as number => x`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x) as number => x`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -745,7 +575,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x as number) as number => x`, async () => { - await runAbridgedNodeTest(`(x as number) as number => x`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x as number) as number => x`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -767,7 +597,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(x as number) as nullable number => x`, async () => { - await runAbridgedNodeTest(`(x as number) as nullable number => x`, [ + await ParserTestUtils.runAbridgedNodeTest(`(x as number) as nullable number => x`, [ [Ast.NodeKind.FunctionExpression, undefined], [Ast.NodeKind.ParameterList, 0], [Ast.NodeKind.Constant, 0], @@ -791,7 +621,7 @@ describe("Parser.AbridgedNode", () => { }); it(`let Fn = () as nullable text => "asd" in Fn`, async () => { - await runAbridgedNodeTest(`let Fn = () as nullable text => "asd" in Fn`, [ + await ParserTestUtils.runAbridgedNodeTest(`let Fn = () as nullable text => "asd" in Fn`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -820,7 +650,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.FunctionType}`, () => { it(`type function () as number`, async () => { - await runAbridgedNodeTest(`type function () as number`, [ + await ParserTestUtils.runAbridgedNodeTest(`type function () as number`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.FunctionType, 1], @@ -836,7 +666,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type function (x as number) as number`, async () => { - await runAbridgedNodeTest(`type function (x as number) as number`, [ + await ParserTestUtils.runAbridgedNodeTest(`type function (x as number) as number`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.FunctionType, 1], @@ -862,7 +692,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.GeneralizedIdentifier}`, () => { it(`[foo bar]`, async () => { - await runAbridgedNodeTest(`[foo bar]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[foo bar]`, [ [Ast.NodeKind.FieldSelector, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.GeneralizedIdentifier, 1], @@ -871,7 +701,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[1]`, async () => { - await runAbridgedNodeTest(`[1]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[1]`, [ [Ast.NodeKind.FieldSelector, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.GeneralizedIdentifier, 1], @@ -880,7 +710,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[a.1]`, async () => { - await runAbridgedNodeTest(`[a.1]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[a.1]`, [ [Ast.NodeKind.FieldSelector, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.GeneralizedIdentifier, 1], @@ -889,7 +719,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[#"a""" = 1]`, async () => { - await runAbridgedNodeTest(`[#"a""" = 1]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[#"a""" = 1]`, [ [Ast.NodeKind.RecordExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -904,7 +734,7 @@ describe("Parser.AbridgedNode", () => { }); it(`Ast.Ast.NodeKind.GeneralizedIdentifierPairedAnyLiteral`, async () => { - await runAbridgedNodeTest(`[x=1] section;`, [ + await ParserTestUtils.runAbridgedNodeTest(`[x=1] section;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.RecordLiteral, 0], [Ast.NodeKind.Constant, 0], @@ -922,7 +752,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.GeneralizedIdentifierPairedExpression}`, async () => { - await runAbridgedNodeTest(`[x=1]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[x=1]`, [ [Ast.NodeKind.RecordExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -939,7 +769,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.IdentifierExpression}`, () => { it(`@foo`, async () => { - await runAbridgedNodeTest(`@foo`, [ + await ParserTestUtils.runAbridgedNodeTest(`@foo`, [ [Ast.NodeKind.IdentifierExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.Identifier, 1], @@ -947,7 +777,7 @@ describe("Parser.AbridgedNode", () => { }); it(`零`, async () => { - await runAbridgedNodeTest(`零`, [ + await ParserTestUtils.runAbridgedNodeTest(`零`, [ [Ast.NodeKind.IdentifierExpression, undefined], [Ast.NodeKind.Identifier, 1], ]); @@ -955,7 +785,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.IdentifierPairedExpression}`, async () => { - await runAbridgedNodeTest(`section; x = 1;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; x = 1;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -970,7 +800,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.IfExpression}`, async () => { - await runAbridgedNodeTest(`if x then x else x`, [ + await ParserTestUtils.runAbridgedNodeTest(`if x then x else x`, [ [Ast.NodeKind.IfExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.IdentifierExpression, 1], @@ -985,7 +815,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.InvokeExpression}`, async () => { - await runAbridgedNodeTest(`foo()`, [ + await ParserTestUtils.runAbridgedNodeTest(`foo()`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -999,7 +829,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.IsExpression}`, () => { it(`1 is`, async () => { - await runAbridgedNodeTest(`1 is`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 is`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.IsExpression, 1], @@ -1009,7 +839,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 is number`, async () => { - await runAbridgedNodeTest(`1 is number`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 is number`, [ [Ast.NodeKind.IsExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1018,7 +848,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 is number is number`, async () => { - await runAbridgedNodeTest(`1 is number is number`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 is number is number`, [ [Ast.NodeKind.IsExpression, undefined], [Ast.NodeKind.IsExpression, 0], [Ast.NodeKind.LiteralExpression, 0], @@ -1031,7 +861,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.ItemAccessExpression}`, async () => { - await runAbridgedNodeTest(`x{1}`, [ + await ParserTestUtils.runAbridgedNodeTest(`x{1}`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -1044,7 +874,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.ItemAccessExpression} optional`, async () => { - await runAbridgedNodeTest(`x{1}?`, [ + await ParserTestUtils.runAbridgedNodeTest(`x{1}?`, [ [Ast.NodeKind.RecursivePrimaryExpression, undefined], [Ast.NodeKind.IdentifierExpression, 0], [Ast.NodeKind.Identifier, 1], @@ -1059,14 +889,14 @@ describe("Parser.AbridgedNode", () => { describe(`keywords`, () => { it(`#sections`, async () => { - await runAbridgedNodeTest(`#sections`, [ + await ParserTestUtils.runAbridgedNodeTest(`#sections`, [ [Ast.NodeKind.IdentifierExpression, undefined], [Ast.NodeKind.Identifier, 1], ]); }); it(`#shared`, async () => { - await runAbridgedNodeTest(`#shared`, [ + await ParserTestUtils.runAbridgedNodeTest(`#shared`, [ [Ast.NodeKind.IdentifierExpression, undefined], [Ast.NodeKind.Identifier, 1], ]); @@ -1075,7 +905,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.LetExpression}`, () => { it(`let in 1`, async () => { - await runAbridgedNodeTest(`let in 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`let in 1`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1085,7 +915,7 @@ describe("Parser.AbridgedNode", () => { }); it(`let x = 1 in x`, async () => { - await runAbridgedNodeTest(`let x = 1 in x`, [ + await ParserTestUtils.runAbridgedNodeTest(`let x = 1 in x`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1101,7 +931,7 @@ describe("Parser.AbridgedNode", () => { }); it(`let x = 1 in try x`, async () => { - await runAbridgedNodeTest(`let x = 1 in try x`, [ + await ParserTestUtils.runAbridgedNodeTest(`let x = 1 in try x`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1119,7 +949,7 @@ describe("Parser.AbridgedNode", () => { }); it(`let a = let argh`, async () => { - await runAbridgedNodeTest(`let a = let argh`, [ + await ParserTestUtils.runAbridgedNodeTest(`let a = let argh`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1140,7 +970,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.ListExpression}`, () => { it(`{}`, async () => { - await runAbridgedNodeTest(`{}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1149,7 +979,7 @@ describe("Parser.AbridgedNode", () => { }); it(`{1, 2}`, async () => { - await runAbridgedNodeTest(`{1, 2}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{1, 2}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1163,7 +993,7 @@ describe("Parser.AbridgedNode", () => { }); it(`{1..2}`, async () => { - await runAbridgedNodeTest(`{1..2}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{1..2}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1177,7 +1007,7 @@ describe("Parser.AbridgedNode", () => { }); it(`{1..2, 3..4}`, async () => { - await runAbridgedNodeTest(`{1..2, 3..4}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{1..2, 3..4}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1197,7 +1027,7 @@ describe("Parser.AbridgedNode", () => { }); it(`{1, 2..3}`, async () => { - await runAbridgedNodeTest(`{1, 2..3}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{1, 2..3}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1214,7 +1044,7 @@ describe("Parser.AbridgedNode", () => { }); it(`{1..2, 3}`, async () => { - await runAbridgedNodeTest(`{1..2, 3}`, [ + await ParserTestUtils.runAbridgedNodeTest(`{1..2, 3}`, [ [Ast.NodeKind.ListExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1231,7 +1061,7 @@ describe("Parser.AbridgedNode", () => { }); it(`let x = 1, y = {x..2} in y`, async () => { - await runAbridgedNodeTest(`let x = 1, y = {x..2} in y`, [ + await ParserTestUtils.runAbridgedNodeTest(`let x = 1, y = {x..2} in y`, [ [Ast.NodeKind.LetExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1264,7 +1094,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.ListLiteral}`, () => { it(`[foo = {1}] section;`, async () => { - await runAbridgedNodeTest(`[foo = {1}] section;`, [ + await ParserTestUtils.runAbridgedNodeTest(`[foo = {1}] section;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.RecordLiteral, 0], [Ast.NodeKind.Constant, 0], @@ -1287,7 +1117,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[foo = {}] section;`, async () => { - await runAbridgedNodeTest(`[foo = {}] section;`, [ + await ParserTestUtils.runAbridgedNodeTest(`[foo = {}] section;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.RecordLiteral, 0], [Ast.NodeKind.Constant, 0], @@ -1309,7 +1139,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.ListType}`, async () => { - await runAbridgedNodeTest(`type {number}`, [ + await ParserTestUtils.runAbridgedNodeTest(`type {number}`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ListType, 1], @@ -1321,69 +1151,69 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.LiteralExpression}`, () => { it(`true`, async () => { - await runAbridgedNodeTest(`true`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`true`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`false`, async () => { - await runAbridgedNodeTest(`false`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`false`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`1`, async () => { - await runAbridgedNodeTest(`1`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`1`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`0x1`, async () => { - await runAbridgedNodeTest(`0x1`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`0x1`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`0X1`, async () => { - await runAbridgedNodeTest(`0X1`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`0X1`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`1.2`, async () => { - await runAbridgedNodeTest(`1.2`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`1.2`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`.1`, async () => { - await runAbridgedNodeTest(".1", [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(".1", [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`1e2`, async () => { - await runAbridgedNodeTest("1e2", [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest("1e2", [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`1e+2`, async () => { - await runAbridgedNodeTest("1e+2", [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest("1e+2", [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`1e-2`, async () => { - await runAbridgedNodeTest("1e-2", [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest("1e-2", [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`#nan`, async () => { - await runAbridgedNodeTest(`#nan`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`#nan`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`#infinity`, async () => { - await runAbridgedNodeTest(`#infinity`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`#infinity`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`""`, async () => { - await runAbridgedNodeTest(`""`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`""`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`""""`, async () => { - await runAbridgedNodeTest(`""""`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`""""`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); it(`null`, async () => { - await runAbridgedNodeTest(`null`, [[Ast.NodeKind.LiteralExpression, undefined]]); + await ParserTestUtils.runAbridgedNodeTest(`null`, [[Ast.NodeKind.LiteralExpression, undefined]]); }); }); describe(`${Ast.NodeKind.LogicalExpression}`, () => { it(`true and true`, async () => { - await runAbridgedNodeTest(`true and true`, [ + await ParserTestUtils.runAbridgedNodeTest(`true and true`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1392,7 +1222,7 @@ describe("Parser.AbridgedNode", () => { }); it(`true or true`, async () => { - await runAbridgedNodeTest(`true or true`, [ + await ParserTestUtils.runAbridgedNodeTest(`true or true`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1402,7 +1232,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.MetadataExpression}`, async () => { - await runAbridgedNodeTest(`1 meta 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 meta 1`, [ [Ast.NodeKind.MetadataExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1411,14 +1241,14 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.NotImplementedExpression}`, async () => { - await runAbridgedNodeTest(`...`, [ + await ParserTestUtils.runAbridgedNodeTest(`...`, [ [Ast.NodeKind.NotImplementedExpression, undefined], [Ast.NodeKind.Constant, 0], ]); }); it(`${Ast.NodeKind.NullablePrimitiveType}`, async () => { - await runAbridgedNodeTest(`1 is nullable number`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 is nullable number`, [ [Ast.NodeKind.IsExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1429,7 +1259,7 @@ describe("Parser.AbridgedNode", () => { }); it(`${Ast.NodeKind.NullableType}`, async () => { - await runAbridgedNodeTest(`type nullable number`, [ + await ParserTestUtils.runAbridgedNodeTest(`type nullable number`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.NullableType, 1], @@ -1440,7 +1270,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.NullCoalescingExpression}`, () => { it(`1 ?? a`, async () => { - await runAbridgedNodeTest(`1 ?? a`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 ?? a`, [ [Ast.NodeKind.NullCoalescingExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1450,7 +1280,7 @@ describe("Parser.AbridgedNode", () => { }); it(`1 ?? 2 ?? 3`, async () => { - await runAbridgedNodeTest(`1 ?? 2 ?? 3`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 ?? 2 ?? 3`, [ [Ast.NodeKind.NullCoalescingExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1470,7 +1300,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.ParenthesizedExpression}`, () => { it(`(1)`, async () => { - await runAbridgedNodeTest(`(1)`, [ + await ParserTestUtils.runAbridgedNodeTest(`(1)`, [ [Ast.NodeKind.ParenthesizedExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.LiteralExpression, 1], @@ -1479,7 +1309,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(1) + 1`, async () => { - await runAbridgedNodeTest(`(1) + 1`, [ + await ParserTestUtils.runAbridgedNodeTest(`(1) + 1`, [ [Ast.NodeKind.ArithmeticExpression, undefined], [Ast.NodeKind.ParenthesizedExpression, 0], [Ast.NodeKind.Constant, 0], @@ -1491,7 +1321,7 @@ describe("Parser.AbridgedNode", () => { }); it(`(if true then true else false) and true`, async () => { - await runAbridgedNodeTest(`(if true then true else false) and true`, [ + await ParserTestUtils.runAbridgedNodeTest(`(if true then true else false) and true`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.ParenthesizedExpression, 0], [Ast.NodeKind.Constant, 0], @@ -1509,7 +1339,7 @@ describe("Parser.AbridgedNode", () => { }); it(`((1)) and true`, async () => { - await runAbridgedNodeTest(`((1)) and true`, [ + await ParserTestUtils.runAbridgedNodeTest(`((1)) and true`, [ [Ast.NodeKind.LogicalExpression, undefined], [Ast.NodeKind.ParenthesizedExpression, 0], [Ast.NodeKind.Constant, 0], @@ -1526,7 +1356,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.PrimitiveType}`, () => { it(`1 as time`, async () => { - await runAbridgedNodeTest(`1 as time`, [ + await ParserTestUtils.runAbridgedNodeTest(`1 as time`, [ [Ast.NodeKind.AsExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1537,7 +1367,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.RecordExpression}`, () => { it(`[x=1]`, async () => { - await runAbridgedNodeTest(`[x=1]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[x=1]`, [ [Ast.NodeKind.RecordExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1551,7 +1381,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[]`, async () => { - await runAbridgedNodeTest(`[]`, [ + await ParserTestUtils.runAbridgedNodeTest(`[]`, [ [Ast.NodeKind.RecordExpression, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.ArrayWrapper, 1], @@ -1564,7 +1394,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.RecordType}`, () => { it(`type [x]`, async () => { - await runAbridgedNodeTest(`type [x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -1579,7 +1409,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type [x, ...]`, async () => { - await runAbridgedNodeTest(`type [x, ...]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type [x, ...]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.RecordType, 1], @@ -1600,7 +1430,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.RelationalExpression}`, () => { it(`1 > 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 > 2`, Constant.RelationalOperator.GreaterThan, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 > 2`, Constant.RelationalOperator.GreaterThan, [ [Ast.NodeKind.RelationalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1609,16 +1439,20 @@ describe("Parser.AbridgedNode", () => { }); it(`1 >= 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 >= 2`, Constant.RelationalOperator.GreaterThanEqualTo, [ - [Ast.NodeKind.RelationalExpression, undefined], - [Ast.NodeKind.LiteralExpression, 0], - [Ast.NodeKind.Constant, 1], - [Ast.NodeKind.LiteralExpression, 2], - ]); + await ParserTestUtils.runAbridgedNodeAndOperatorTest( + `1 >= 2`, + Constant.RelationalOperator.GreaterThanEqualTo, + [ + [Ast.NodeKind.RelationalExpression, undefined], + [Ast.NodeKind.LiteralExpression, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.LiteralExpression, 2], + ], + ); }); it(`1 < 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 < 2`, Constant.RelationalOperator.LessThan, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`1 < 2`, Constant.RelationalOperator.LessThan, [ [Ast.NodeKind.RelationalExpression, undefined], [Ast.NodeKind.LiteralExpression, 0], [Ast.NodeKind.Constant, 1], @@ -1627,18 +1461,22 @@ describe("Parser.AbridgedNode", () => { }); it(`1 <= 2`, async () => { - await runAbridgedNodeAndOperatorTest(`1 <= 2`, Constant.RelationalOperator.LessThanEqualTo, [ - [Ast.NodeKind.RelationalExpression, undefined], - [Ast.NodeKind.LiteralExpression, 0], - [Ast.NodeKind.Constant, 1], - [Ast.NodeKind.LiteralExpression, 2], - ]); + await ParserTestUtils.runAbridgedNodeAndOperatorTest( + `1 <= 2`, + Constant.RelationalOperator.LessThanEqualTo, + [ + [Ast.NodeKind.RelationalExpression, undefined], + [Ast.NodeKind.LiteralExpression, 0], + [Ast.NodeKind.Constant, 1], + [Ast.NodeKind.LiteralExpression, 2], + ], + ); }); }); describe(`${Ast.NodeKind.Section}`, () => { it(`section;`, async () => { - await runAbridgedNodeTest(`section;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1647,7 +1485,7 @@ describe("Parser.AbridgedNode", () => { }); it(`[] section;`, async () => { - await runAbridgedNodeTest(`[] section;`, [ + await ParserTestUtils.runAbridgedNodeTest(`[] section;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.RecordLiteral, 0], [Ast.NodeKind.Constant, 0], @@ -1660,7 +1498,7 @@ describe("Parser.AbridgedNode", () => { }); it(`section foo;`, async () => { - await runAbridgedNodeTest(`section foo;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section foo;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Identifier, 2], @@ -1670,7 +1508,7 @@ describe("Parser.AbridgedNode", () => { }); it(`section; x = 1;`, async () => { - await runAbridgedNodeTest(`section; x = 1;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; x = 1;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1685,7 +1523,7 @@ describe("Parser.AbridgedNode", () => { }); it(`section; x = 1; y = 2;`, async () => { - await runAbridgedNodeTest(`section; x = 1; y = 2;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; x = 1; y = 2;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1708,7 +1546,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.SectionMember}`, () => { it(`section; x = 1;`, async () => { - await runAbridgedNodeTest(`section; x = 1;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; x = 1;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1723,7 +1561,7 @@ describe("Parser.AbridgedNode", () => { }); it(`section; [] x = 1;`, async () => { - await runAbridgedNodeTest(`section; [] x = 1;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; [] x = 1;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1742,7 +1580,7 @@ describe("Parser.AbridgedNode", () => { }); it(`section; shared x = 1;`, async () => { - await runAbridgedNodeTest(`section; shared x = 1;`, [ + await ParserTestUtils.runAbridgedNodeTest(`section; shared x = 1;`, [ [Ast.NodeKind.Section, undefined], [Ast.NodeKind.Constant, 1], [Ast.NodeKind.Constant, 3], @@ -1760,7 +1598,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.TableType}`, () => { it(`type table [x]`, async () => { - await runAbridgedNodeTest(`type table [x]`, [ + await ParserTestUtils.runAbridgedNodeTest(`type table [x]`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.TableType, 1], @@ -1776,7 +1614,7 @@ describe("Parser.AbridgedNode", () => { }); it(`type table (x)`, async () => { - await runAbridgedNodeTest(`type table (x)`, [ + await ParserTestUtils.runAbridgedNodeTest(`type table (x)`, [ [Ast.NodeKind.TypePrimaryType, undefined], [Ast.NodeKind.Constant, 0], [Ast.NodeKind.TableType, 1], @@ -1794,7 +1632,7 @@ describe("Parser.AbridgedNode", () => { describe(`${Ast.NodeKind.UnaryExpression}`, () => { it(`-1`, async () => { - await runAbridgedNodeAndOperatorTest(`-1`, Constant.UnaryOperator.Negative, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`-1`, Constant.UnaryOperator.Negative, [ [Ast.NodeKind.UnaryExpression, undefined], [Ast.NodeKind.ArrayWrapper, 0], [Ast.NodeKind.Constant, 0], @@ -1803,7 +1641,7 @@ describe("Parser.AbridgedNode", () => { }); it(`not 1`, async () => { - await runAbridgedNodeAndOperatorTest(`not 1`, Constant.UnaryOperator.Not, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`not 1`, Constant.UnaryOperator.Not, [ [Ast.NodeKind.UnaryExpression, undefined], [Ast.NodeKind.ArrayWrapper, 0], [Ast.NodeKind.Constant, 0], @@ -1812,7 +1650,7 @@ describe("Parser.AbridgedNode", () => { }); it(`+1`, async () => { - await runAbridgedNodeAndOperatorTest(`+1`, Constant.UnaryOperator.Positive, [ + await ParserTestUtils.runAbridgedNodeAndOperatorTest(`+1`, Constant.UnaryOperator.Positive, [ [Ast.NodeKind.UnaryExpression, undefined], [Ast.NodeKind.ArrayWrapper, 0], [Ast.NodeKind.Constant, 0], diff --git a/src/test/libraryTest/parser/parserTestUtils.ts b/src/test/libraryTest/parser/parserTestUtils.ts new file mode 100644 index 00000000..87fd291a --- /dev/null +++ b/src/test/libraryTest/parser/parserTestUtils.ts @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "mocha"; +import { expect } from "chai"; + +import { Assert, DefaultLocale, DefaultSettings, ResultUtils, Task, TaskUtils, Traverse } from "../../.."; +import { Ast, Constant } from "../../../powerquery-parser/language"; +import { NodeIdMap, ParseSettings, TXorNode, XorNodeUtils } from "../../../powerquery-parser/parser"; +import { AssertTestUtils } from "../../testUtils"; +import { LexSettings } from "../../../powerquery-parser/lexer"; +import { NoOpTraceManagerInstance } from "../../../powerquery-parser/common/trace"; + +export type AbridgedNode = [Ast.NodeKind, number | undefined]; + +type CollectAbridgeNodeState = Traverse.ITraversalState; + +interface NthNodeOfKindState extends Traverse.ITraversalState { + readonly nodeKind: Ast.NodeKind; + readonly nthRequired: number; + nthCounter: number; +} + +export async function runAbridgedNodeTest( + text: string, + expected: ReadonlyArray, + options?: { + readonly astOnly?: boolean; + readonly settings: LexSettings & ParseSettings; + }, +): Promise { + const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse( + options?.settings ?? DefaultSettings, + text, + ); + + let root: TXorNode; + let nodeIdMapCollection: NodeIdMap.Collection; + + if (TaskUtils.isParseStageOk(triedLexParse)) { + root = XorNodeUtils.boxAst(triedLexParse.ast); + nodeIdMapCollection = triedLexParse.nodeIdMapCollection; + } else if (TaskUtils.isParseStageParseError(triedLexParse)) { + root = XorNodeUtils.boxContext(Assert.asDefined(triedLexParse.parseState.contextState.root)); + nodeIdMapCollection = triedLexParse.nodeIdMapCollection; + } else { + throw new Error(`expected isParseStageOk/isParseStageParseError`); + } + + validateNodeIdMapCollection(nodeIdMapCollection, root); + + const actual: ReadonlyArray = await collectAbridgeNodeFromXor( + nodeIdMapCollection, + root, + options?.astOnly ?? false, + ); + + expect(actual).to.deep.equal(expected); + + return triedLexParse; +} + +export async function runAbridgedNodeAndOperatorTest( + text: string, + constant: Constant.TConstant, + expected: ReadonlyArray, +): Promise { + await runAbridgedNodeTest(text, expected); + + const operatorNode: Ast.TConstant = await assertGetNthNodeOfKind(text, Ast.NodeKind.Constant, 1); + + expect(operatorNode.constantKind).to.equal(constant); +} + +export async function assertGetNthNodeOfKind( + text: string, + nodeKind: Ast.NodeKind, + nthRequired: number, +): Promise { + const parseTaskOk: Task.ParseTaskOk = await AssertTestUtils.assertGetLexParseOk(DefaultSettings, text); + + const state: NthNodeOfKindState = { + locale: DefaultLocale, + result: undefined, + nodeKind, + nthCounter: 0, + nthRequired, + cancellationToken: undefined, + initialCorrelationId: undefined, + traceManager: NoOpTraceManagerInstance, + }; + + const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseAst< + NthNodeOfKindState, + Ast.TNode | undefined + >( + state, + parseTaskOk.nodeIdMapCollection, + parseTaskOk.ast, + Traverse.VisitNodeStrategy.BreadthFirst, + nthNodeVisit, + Traverse.assertGetAllAstChildren, + nthNodeEarlyExit, + ); + + ResultUtils.assertIsOk(triedTraverse); + + return Assert.asDefined(triedTraverse.value) as N; +} + +async function collectAbridgeNodeFromXor( + nodeIdMapCollection: NodeIdMap.Collection, + root: TXorNode, + astOnly: boolean, +): Promise> { + const state: CollectAbridgeNodeState = { + locale: DefaultLocale, + result: [], + cancellationToken: undefined, + initialCorrelationId: undefined, + traceManager: NoOpTraceManagerInstance, + }; + + const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseXor< + CollectAbridgeNodeState, + AbridgedNode[] + >( + state, + nodeIdMapCollection, + root, + Traverse.VisitNodeStrategy.BreadthFirst, + (state: CollectAbridgeNodeState, xorNode: TXorNode) => collectAbridgeXorNodeVisit(state, xorNode, astOnly), + Traverse.assertGetAllXorChildren, + undefined, + ); + + ResultUtils.assertIsOk(triedTraverse); + + return triedTraverse.value; +} + +// eslint-disable-next-line require-await +async function collectAbridgeXorNodeVisit( + state: CollectAbridgeNodeState, + xorNode: TXorNode, + astOnly: boolean, +): Promise { + if (astOnly && !XorNodeUtils.isAst(xorNode)) { + return; + } + + state.result.push([xorNode.node.kind, xorNode.node.attributeIndex]); +} + +// eslint-disable-next-line require-await +async function nthNodeVisit(state: NthNodeOfKindState, node: Ast.TNode): Promise { + if (node.kind === state.nodeKind) { + state.nthCounter += 1; + + if (state.nthCounter === state.nthRequired) { + state.result = node; + } + } +} + +// eslint-disable-next-line require-await +async function nthNodeEarlyExit(state: NthNodeOfKindState, _: Ast.TNode): Promise { + return state.nthCounter === state.nthRequired; +} + +function validateNodeIdMapCollection(nodeIdMapCollection: NodeIdMap.Collection, root: TXorNode): void { + const astNodeIds: Set = new Set(nodeIdMapCollection.astNodeById.keys()); + const contextNodeIds: Set = new Set(nodeIdMapCollection.contextNodeById.keys()); + const allNodeIds: Set = new Set([...astNodeIds].concat([...contextNodeIds])); + + expect(nodeIdMapCollection.parentIdById).to.not.have.key(root.node.id.toString()); + + expect(nodeIdMapCollection.parentIdById.size).to.equal( + allNodeIds.size - 1, + "parentIdById should have one less entry than allNodeIds", + ); + + expect(astNodeIds.size + contextNodeIds.size).to.equal( + allNodeIds.size, + "allNodeIds should be a union of astNodeIds and contextNodeIds", + ); + + for (const [childId, parentId] of nodeIdMapCollection.parentIdById.entries()) { + expect(allNodeIds).to.include(childId, "keys for parentIdById should be in allNodeIds"); + expect(allNodeIds).to.include(parentId, "values for parentIdById should be in allNodeIds"); + } + + for (const [parentId, childrenIds] of nodeIdMapCollection.childIdsById.entries()) { + expect(allNodeIds).to.include(parentId, "keys for childIdsById should be in allNodeIds"); + + for (const childId of childrenIds) { + expect(allNodeIds).to.include(childId, "childIds should be in allNodeIds"); + + if (astNodeIds.has(parentId)) { + expect(astNodeIds).to.include(childId, "if a parent is an astNode then so should be its children"); + } + } + } +}