From a81b1858429f617516a2d4c930622698aa8092ce Mon Sep 17 00:00:00 2001 From: "Jordan Bolton (jobolton)" Date: Mon, 8 Jun 2026 13:52:54 -0500 Subject: [PATCH 1/3] Enable noUncheckedIndexedAccess and fix all errors with Assert.asDefined Adds oUncheckedIndexedAccess: true to tsconfig.json and wraps all indexed array/string accesses with Assert.asDefined() to satisfy the stricter type checking. This provides runtime safety for all array index accesses that were previously unchecked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/powerquery-parser/common/stringUtils.ts | 4 +-- .../language/identifierUtils.ts | 2 +- .../language/type/typeUtils/factories.ts | 2 +- .../language/type/typeUtils/isCompatible.ts | 2 +- .../language/type/typeUtils/isEqualType.ts | 10 +++--- .../language/type/typeUtils/typeCheck.ts | 8 ++--- src/powerquery-parser/lexer/lexer.ts | 34 +++++++++---------- src/powerquery-parser/lexer/lexerSnapshot.ts | 4 +-- .../parser/context/contextUtils.ts | 2 +- .../disambiguation/disambiguationUtils.ts | 6 ++-- .../parser/nodeIdMap/nodeIdMapIterator.ts | 2 +- .../nodeIdMap/nodeIdMapUtils/idUtils.ts | 6 ++-- .../nodeIdMap/nodeIdMapUtils/leafSelectors.ts | 2 +- .../nodeIdMapUtils/nodeIdMapUtils.ts | 2 +- .../parser/parseState/parseStateUtils.ts | 2 +- .../parser/parsers/naiveParseSteps.ts | 12 +++---- .../language/typeUtils/typeCheck.test.ts | 11 +++--- .../language/typeUtils/typeUtils.test.ts | 5 +-- src/test/libraryTest/lexer/lexError.test.ts | 3 +- .../libraryTest/lexer/lexIncremental.test.ts | 25 +++++++------- .../lexer/lexerSnapshotCache.test.ts | 13 +++---- .../lexer/retokenizeLineNumbers.test.ts | 7 ++-- .../parser/identifierContextKind.test.ts | 3 +- .../parser/parseNodeIdMapUtils.test.ts | 18 +++++----- .../libraryTest/parser/typeDirective.test.ts | 11 +++--- .../tokenizer/tokenizerIncremental.test.ts | 21 +++++++----- .../tokenizer/tokenizerSimple.test.ts | 7 ++-- src/test/testUtils/tokenizerTestUtils.ts | 7 ++-- tsconfig.json | 1 + 29 files changed, 123 insertions(+), 109 deletions(-) diff --git a/src/powerquery-parser/common/stringUtils.ts b/src/powerquery-parser/common/stringUtils.ts index 79380b8d..d1cfefcd 100644 --- a/src/powerquery-parser/common/stringUtils.ts +++ b/src/powerquery-parser/common/stringUtils.ts @@ -162,7 +162,7 @@ export function findQuotes(text: string, indexStart: number): FoundQuotes | unde } export function newlineKindAt(text: string, index: number): NewlineKind | undefined { - const chr1: string = text[index]; + const chr1: string | undefined = text[index]; switch (chr1) { case `\u000d`: { @@ -199,7 +199,7 @@ export function isHex(text: string): boolean { } export function getSign(text: string): [boolean, number] { - let char: string = text[0]; + let char: string | undefined = text[0]; let charOffset: number = 0; let isPositive: boolean = true; diff --git a/src/powerquery-parser/language/identifierUtils.ts b/src/powerquery-parser/language/identifierUtils.ts index f17d94ca..ea87f40f 100644 --- a/src/powerquery-parser/language/identifierUtils.ts +++ b/src/powerquery-parser/language/identifierUtils.ts @@ -271,7 +271,7 @@ function getGeneralizedIdentifierLength(text: string, index: number): number | u let continueMatching: boolean = true; while (continueMatching) { - const currentChr: string = text[index]; + const currentChr: string | undefined = text[index]; if (currentChr === " ") { index += 1; diff --git a/src/powerquery-parser/language/type/typeUtils/factories.ts b/src/powerquery-parser/language/type/typeUtils/factories.ts index e5a4b0a5..09acb834 100644 --- a/src/powerquery-parser/language/type/typeUtils/factories.ts +++ b/src/powerquery-parser/language/type/typeUtils/factories.ts @@ -31,7 +31,7 @@ export function anyUnion( if (simplified.length === 1) { trace.exit(); - return simplified[0]; + return Assert.asDefined(simplified[0]); } const result: Type.AnyUnion = { diff --git a/src/powerquery-parser/language/type/typeUtils/isCompatible.ts b/src/powerquery-parser/language/type/typeUtils/isCompatible.ts index 9c7ecf5a..315bf82a 100644 --- a/src/powerquery-parser/language/type/typeUtils/isCompatible.ts +++ b/src/powerquery-parser/language/type/typeUtils/isCompatible.ts @@ -777,7 +777,7 @@ function isCompatibleDefinedListOrDefinedListType - isEqualType(leftType, rightElements[index]), + isEqualType(leftType, Assert.asDefined(rightElements[index])), ), ); } @@ -160,7 +160,7 @@ export function isEqualDefinedListType(left: Type.DefinedListType, right: Type.D return ArrayUtils.all( left.itemTypes.map((leftType: Type.TPowerQueryType, index: number) => - isEqualType(leftType, rightElements[index]), + isEqualType(leftType, Assert.asDefined(rightElements[index])), ), ); } @@ -224,8 +224,8 @@ export function isEqualFunctionParameters( const numParameters: number = left.length; for (let index: number = 0; index < numParameters; index += 1) { - const nthLeft: Type.FunctionParameter = left[index]; - const nthRight: Type.FunctionParameter = right[index]; + const nthLeft: Type.FunctionParameter = Assert.asDefined(left[index]); + const nthRight: Type.FunctionParameter = Assert.asDefined(right[index]); if (!isEqualFunctionParameter(nthLeft, nthRight)) { return false; diff --git a/src/powerquery-parser/language/type/typeUtils/typeCheck.ts b/src/powerquery-parser/language/type/typeUtils/typeCheck.ts index 46f21c7a..f0217f54 100644 --- a/src/powerquery-parser/language/type/typeUtils/typeCheck.ts +++ b/src/powerquery-parser/language/type/typeUtils/typeCheck.ts @@ -3,7 +3,7 @@ import { isCompatible, isCompatibleWithFunctionParameter } from "./isCompatible"; import { Trace, TraceManager } from "../../../common/trace"; -import { ArrayUtils } from "../../../common"; +import { ArrayUtils, Assert } from "../../../common"; import { isEqualFunctionParameter } from "./isEqualType"; import { Type } from ".."; import { TypeUtilsTraceConstant } from "./typeTraceConstant"; @@ -127,7 +127,7 @@ export function typeCheckInvocation( for (let index: number = 0; index < numParameters; index += 1) { const arg: Type.TPowerQueryType | undefined = args[index]; - const parameter: Type.FunctionParameter = parameters[index]; + const parameter: Type.FunctionParameter = Assert.asDefined(parameters[index]); if (isCompatibleWithFunctionParameter(arg, parameter)) { validArgs.push(index); @@ -257,8 +257,8 @@ function typeCheckGenericNumber< const mismatches: Map> = new Map(); for (let index: number = 0; index < upperBound; index += 1) { - const element: Value = valueElements[index]; - const schemaItemType: Schema = schemaItemTypes[index]; + const element: Value = Assert.asDefined(valueElements[index]); + const schemaItemType: Schema = Assert.asDefined(schemaItemTypes[index]); if (comparer(element, schemaItemType, traceManager, trace.id)) { validIndices.push(index); diff --git a/src/powerquery-parser/lexer/lexer.ts b/src/powerquery-parser/lexer/lexer.ts index aa69d603..a74b9ad4 100644 --- a/src/powerquery-parser/lexer/lexer.ts +++ b/src/powerquery-parser/lexer/lexer.ts @@ -148,8 +148,8 @@ export function equalLines(leftLines: ReadonlyArray, rightLines: Readonly const numLines: number = leftLines.length; for (let lineIndex: number = 0; lineIndex < numLines; lineIndex += 1) { - const left: TLine = leftLines[lineIndex]; - const right: TLine = rightLines[lineIndex]; + const left: TLine = Assert.asDefined(leftLines[lineIndex]); + const right: TLine = Assert.asDefined(rightLines[lineIndex]); const leftTokens: ReadonlyArray = left.tokens; const rightTokens: ReadonlyArray = right.tokens; @@ -168,7 +168,7 @@ export function equalLines(leftLines: ReadonlyArray, rightLines: Readonly const numTokens: number = leftTokens.length; for (let tokenIndex: number = 0; tokenIndex < numTokens; tokenIndex += 1) { - if (!equalTokens(leftTokens[tokenIndex], rightTokens[tokenIndex])) { + if (!equalTokens(Assert.asDefined(leftTokens[tokenIndex]), Assert.asDefined(rightTokens[tokenIndex]))) { return false; } } @@ -265,7 +265,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { let indexWasExpanded: boolean = false; for (const lineTerminator of lineTerminators) { - const splitLine: SplitLine = lines[index]; + const splitLine: SplitLine = Assert.asDefined(lines[index]); const text: string = splitLine.text; if (text.indexOf(lineTerminator) !== -1) { @@ -276,7 +276,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { lineTerminator, })); - split[split.length - 1].lineTerminator = splitLine.lineTerminator; + Assert.asDefined(split[split.length - 1]).lineTerminator = splitLine.lineTerminator; lines = [...lines.slice(0, index), ...split, ...lines.slice(index + 1)]; } @@ -287,7 +287,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { } } - lines[lines.length - 1].lineTerminator = ""; + Assert.asDefined(lines[lines.length - 1]).lineTerminator = ""; return lines; } @@ -356,7 +356,7 @@ function updateLine(state: State, lineNumber: number, text: string): State { throw error; } - const line: TLine = state.lines[lineNumber]; + const line: TLine = Assert.asDefined(state.lines[lineNumber]); const range: Range = rangeFrom(line, lineNumber); return updateRange(state, range, text); @@ -374,14 +374,14 @@ function updateRange(state: State, range: Range, text: string): State { const splitLines: SplitLine[] = splitOnLineTerminators(text); const rangeStart: RangePosition = range.start; - const lineStart: TLine = state.lines[rangeStart.lineNumber]; + const lineStart: TLine = Assert.asDefined(state.lines[rangeStart.lineNumber]); const textPrefix: string = lineStart.text.substring(0, rangeStart.lineCodeUnit); - splitLines[0].text = textPrefix + splitLines[0].text; + Assert.asDefined(splitLines[0]).text = textPrefix + Assert.asDefined(splitLines[0]).text; const rangeEnd: RangePosition = range.end; - const lineEnd: TLine = state.lines[rangeEnd.lineNumber]; + const lineEnd: TLine = Assert.asDefined(state.lines[rangeEnd.lineNumber]); const textSuffix: string = lineEnd.text.substr(rangeEnd.lineCodeUnit); - const lastSplitLine: SplitLine = splitLines[splitLines.length - 1]; + const lastSplitLine: SplitLine = Assert.asDefined(splitLines[splitLines.length - 1]); lastSplitLine.text = lastSplitLine.text + textSuffix; // make sure we have a line terminator @@ -400,7 +400,7 @@ function updateRange(state: State, range: Range, text: string): State { const lines: ReadonlyArray = [ ...state.lines.slice(0, rangeStart.lineNumber), ...newLines, - ...retokenizeLines(state, rangeEnd.lineNumber + 1, newLines[newLines.length - 1].lineModeEnd), + ...retokenizeLines(state, rangeEnd.lineNumber + 1, Assert.asDefined(newLines[newLines.length - 1]).lineModeEnd), ]; return { @@ -485,7 +485,7 @@ function retokenizeLines(state: State, lineNumber: number, previousLineModeEnd: const retokenizedLines: TLine[] = []; - if (previousLineModeEnd !== lines[lineNumber].lineModeStart) { + if (previousLineModeEnd !== Assert.asDefined(lines[lineNumber]).lineModeStart) { let currentLine: TLine | undefined = lines[lineNumber]; while (currentLine) { @@ -793,7 +793,7 @@ function tokenizeTextLiteralContentOrEnd(line: TLine, currentPosition: number): function tokenizeDefault(line: TLine, lineNumber: number, positionStart: number, locale: string): LineModeAlteringRead { const text: string = line.text; - const chr1: string = text[positionStart]; + const chr1: string = Assert.asDefined(text[positionStart]); let token: Token.LineToken; let lineMode: LineMode = LineMode.Default; @@ -855,7 +855,7 @@ function tokenizeDefault(line: TLine, lineNumber: number, positionStart: number, } else if ("1" <= chr2 && chr2 <= "9") { token = readNumericLiteral(text, lineNumber, positionStart, locale); } else if (chr2 === ".") { - const chr3: string = text[positionStart + 2]; + const chr3: string = Assert.asDefined(text[positionStart + 2]); if (chr3 === ".") { token = readConstant(Token.LineTokenKind.Ellipsis, text, positionStart, 3); @@ -1321,8 +1321,8 @@ function testBadRangeError(state: State, range: Range): LexError.BadRangeError | const rangeStart: RangePosition = range.start; const rangeEnd: RangePosition = range.end; - const lineStart: TLine = lines[rangeStart.lineNumber]; - const lineEnd: TLine = lines[rangeEnd.lineNumber]; + const lineStart: TLine = Assert.asDefined(lines[rangeStart.lineNumber]); + const lineEnd: TLine = Assert.asDefined(lines[rangeEnd.lineNumber]); if (rangeStart.lineCodeUnit > lineStart.text.length) { kind = LexError.BadRangeKind.LineCodeUnitStart_GreaterThan_LineLength; diff --git a/src/powerquery-parser/lexer/lexerSnapshot.ts b/src/powerquery-parser/lexer/lexerSnapshot.ts index e3bd8754..e5a07d29 100644 --- a/src/powerquery-parser/lexer/lexerSnapshot.ts +++ b/src/powerquery-parser/lexer/lexerSnapshot.ts @@ -135,7 +135,7 @@ function createSnapshot(state: Lexer.State): LexerSnapshot { while (flatIndex < numFlatTokens) { state.cancellationToken?.throwIfCancelled(); - const flatToken: FlatLineToken = flatTokens[flatIndex]; + const flatToken: FlatLineToken = Assert.asDefined(flatTokens[flatIndex]); const lineTokenKind: Token.LineTokenKind = flatToken.kind; switch (lineTokenKind) { @@ -553,7 +553,7 @@ function collectWhileContent( while (flatIndex < numTokens) { cancellationToken?.throwIfCancelled(); - const token: FlatLineToken = flatTokens[flatIndex]; + const token: FlatLineToken = Assert.asDefined(flatTokens[flatIndex]); if (token.kind !== contentKind) { break; diff --git a/src/powerquery-parser/parser/context/contextUtils.ts b/src/powerquery-parser/parser/context/contextUtils.ts index 28908f73..97252604 100644 --- a/src/powerquery-parser/parser/context/contextUtils.ts +++ b/src/powerquery-parser/parser/context/contextUtils.ts @@ -335,7 +335,7 @@ export function deleteContext(state: ParseContext.State, nodeId: number): ParseC // Not a leaf node. if (childIds !== undefined) { ArrayUtils.assertNonZeroLength(childIds); - const childId: number = childIds[0]; + const childId: number = Assert.asDefined(childIds[0]); // Not a leaf node, is the Root node. // Promote the child to the root if it's a Context node. diff --git a/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts b/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts index f195c0a4..74b5fd18 100644 --- a/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts +++ b/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts @@ -213,7 +213,7 @@ export async function disambiguateParenthesis( let offsetTokenIndex: number = initialTokenIndex + 1; while (offsetTokenIndex < totalTokens) { - const offsetTokenKind: Token.TokenKind = tokens[offsetTokenIndex].kind; + const offsetTokenKind: Token.TokenKind = Assert.asDefined(tokens[offsetTokenIndex]).kind; if (offsetTokenKind === Token.TokenKind.LeftParenthesis) { nestedDepth += 1; @@ -305,7 +305,7 @@ export function disambiguateBracket( offsetTokenIndex += 1; while (offsetTokenIndex < totalTokens) { - offsetTokenKind = tokens[offsetTokenIndex].kind; + offsetTokenKind = Assert.asDefined(tokens[offsetTokenIndex]).kind; if (offsetTokenKind === Token.TokenKind.Equal) { result = BracketDisambiguation.RecordExpression; @@ -472,7 +472,7 @@ function unsafeMoveTo(state: ParseState, tokenIndex: number): void { state.tokenIndex = tokenIndex; if (tokenIndex < tokens.length) { - state.currentToken = tokens[tokenIndex]; + state.currentToken = Assert.asDefined(tokens[tokenIndex]); state.currentTokenKind = state.currentToken.kind; } else { state.currentToken = undefined; diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts index 4d485c37..9ddc467d 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts @@ -128,7 +128,7 @@ export function nthSiblingXor( return undefined; } - return NodeIdMapUtils.xor(nodeIdMapCollection, childIds[attributeIndex]); + return NodeIdMapUtils.xor(nodeIdMapCollection, Assert.asDefined(childIds[attributeIndex])); } // ------------------------------------------ diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts index 0850794c..c9011099 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { MapUtils, TypeScriptUtils } from "../../../common"; +import { Assert, MapUtils, TypeScriptUtils } from "../../../common"; import { Trace, TraceConstant, TraceManager } from "../../../common/trace"; import { Ast } from "../../../language"; import { Collection } from "../nodeIdMap"; @@ -63,8 +63,8 @@ export function recalculateIds( const newIdByOldId: Map = new Map(); for (let index: number = 0; index < numIds; index += 1) { - const oldId: number = encounteredIds[index]; - const newId: number = sortedIds[index]; + const oldId: number = Assert.asDefined(encounteredIds[index]); + const newId: number = Assert.asDefined(sortedIds[index]); if (oldId !== newId) { newIdByOldId.set(oldId, newId); diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts index f8ee17ef..61ba0124 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts @@ -35,7 +35,7 @@ export function leftMostXor(nodeIdMapCollection: Collection, nodeId: number): TX let childIds: ReadonlyArray | undefined = nodeIdMapCollection.childIdsById.get(currentNodeId); while (childIds?.length) { - currentNodeId = childIds[0]; + currentNodeId = Assert.asDefined(childIds[0]); childIds = nodeIdMapCollection.childIdsById.get(currentNodeId); } diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts index d7a92f13..3a8c4eb5 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts @@ -56,7 +56,7 @@ export function hasParsedToken(nodeIdMapCollection: Collection, nodeId: number): } // There might be a child under here. else if (numChildren === 1) { - const childId: number = childIds[0]; + const childId: number = Assert.asDefined(childIds[0]); // We know it's an Ast Node, therefore something was parsed. if (nodeIdMapCollection.astNodeById.has(childId)) { diff --git a/src/powerquery-parser/parser/parseState/parseStateUtils.ts b/src/powerquery-parser/parser/parseState/parseStateUtils.ts index 80320754..175e73b9 100644 --- a/src/powerquery-parser/parser/parseState/parseStateUtils.ts +++ b/src/powerquery-parser/parser/parseState/parseStateUtils.ts @@ -162,7 +162,7 @@ export function isOnTokenKind( export function isOnConstantKind(state: ParseState, constantKind: Constant.TConstant): boolean { if (isOnTokenKind(state, Token.TokenKind.Identifier)) { - const currentToken: Token.Token = state.lexerSnapshot.tokens[state.tokenIndex]; + const currentToken: Token.Token = Assert.asDefined(state.lexerSnapshot.tokens[state.tokenIndex]); if (currentToken?.data === undefined) { const details: { currentToken: Token.Token } = { currentToken }; diff --git a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts index 7f182ebb..92b34f2d 100644 --- a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts +++ b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts @@ -122,8 +122,8 @@ export async function readGeneralizedIdentifier( const lexerSnapshot: LexerSnapshot = state.lexerSnapshot; const tokens: ReadonlyArray = lexerSnapshot.tokens; - const contiguousIdentifierStartIndex: number = tokens[tokenRangeStartIndex].positionStart.codeUnit; - const contiguousIdentifierEndIndex: number = tokens[tokenRangeEndIndex - 1].positionEnd.codeUnit; + const contiguousIdentifierStartIndex: number = Assert.asDefined(tokens[tokenRangeStartIndex]).positionStart.codeUnit; + const contiguousIdentifierEndIndex: number = Assert.asDefined(tokens[tokenRangeEndIndex - 1]).positionEnd.codeUnit; const literal: string = lexerSnapshot.text.slice(contiguousIdentifierStartIndex, contiguousIdentifierEndIndex); const literalKind: IdentifierUtils.IdentifierKind = IdentifierUtils.getIdentifierKind(literal, { @@ -2806,7 +2806,7 @@ async function tryReadPrimitiveType( let primitiveTypeKind: Constant.PrimitiveTypeConstant; if (ParseStateUtils.isOnTokenKind(state, TokenKind.Identifier)) { - const currentTokenData: string = state.lexerSnapshot.tokens[state.tokenIndex].data; + const currentTokenData: string = Assert.asDefined(state.lexerSnapshot.tokens[state.tokenIndex]).data; switch (currentTokenData) { case Constant.PrimitiveTypeConstant.Action: @@ -3602,7 +3602,7 @@ export function readToken(state: ParseState): string { tokensLength: tokens.length, }); - const data: string = tokens[state.tokenIndex].data; + const data: string = Assert.asDefined(tokens[state.tokenIndex]).data; state.tokenIndex += 1; if (state.tokenIndex === tokens.length) { @@ -3615,7 +3615,7 @@ export function readToken(state: ParseState): string { // So, for now when a IParseState is Eof when currentTokenKind === undefined. state.currentTokenKind = undefined; } else { - state.currentToken = tokens[state.tokenIndex]; + state.currentToken = Assert.asDefined(tokens[state.tokenIndex]); state.currentTokenKind = state.currentToken.kind; } @@ -3867,7 +3867,7 @@ function testCatchFunction( if ( parameters.length > 1 || - (parameters.length === 1 && parameters[0].node.parameterType) || + (parameters.length === 1 && Assert.asDefined(parameters[0]).node.parameterType) || catchFunction.functionReturnType ) { const tokenStart: Token.Token = Assert.asDefined( diff --git a/src/test/libraryTest/language/typeUtils/typeCheck.test.ts b/src/test/libraryTest/language/typeUtils/typeCheck.test.ts index 158c157e..da6da51f 100644 --- a/src/test/libraryTest/language/typeUtils/typeCheck.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeCheck.test.ts @@ -7,6 +7,7 @@ import { expect } from "chai"; import { CheckedDefinedList, CheckedInvocation } from "../../../../powerquery-parser/language/type/typeUtils"; import { Type, TypeUtils } from "../../../../powerquery-parser/language"; import { Language } from "../../../.."; +import { Assert } from "../../../../powerquery-parser/common"; import { NoOpTraceManagerInstance } from "../../../../powerquery-parser/common/trace"; import { OrderedMap } from "../../../../powerquery-parser"; @@ -300,7 +301,7 @@ describe(`TypeUtils.typeCheck`, () => { 0, { actual: args[0], - expected: definedFunction.parameters[0], + expected: Assert.asDefined(definedFunction.parameters[0]), }, ], ]), @@ -359,7 +360,7 @@ describe(`TypeUtils.typeCheck`, () => { const expected: TypeUtils.CheckedInvocation = { valid: [], - invalid: new Map([[0, { actual: args[0], expected: definedFunction.parameters[0] }]]), + invalid: new Map([[0, { actual: args[0], expected: Assert.asDefined(definedFunction.parameters[0]) }]]), extraneous: [], missing: [], }; @@ -387,7 +388,7 @@ describe(`TypeUtils.typeCheck`, () => { const expected: TypeUtils.CheckedInvocation = { valid: [], - invalid: new Map([[0, { actual: args[0], expected: definedFunction.parameters[0] }]]), + invalid: new Map([[0, { actual: args[0], expected: Assert.asDefined(definedFunction.parameters[0]) }]]), extraneous: [], missing: [], }; @@ -429,14 +430,14 @@ describe(`TypeUtils.typeCheck`, () => { 0, { actual: args[0], - expected: definedFunction.parameters[0], + expected: Assert.asDefined(definedFunction.parameters[0]), }, ], [ 1, { actual: args[1], - expected: definedFunction.parameters[1], + expected: Assert.asDefined(definedFunction.parameters[1]), }, ], ]), diff --git a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts index 59bd9413..056dac8e 100644 --- a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts @@ -7,6 +7,7 @@ import { expect } from "chai"; import { Type, TypeUtils } from "../../../../powerquery-parser/language"; import { NoOpTraceManagerInstance } from "../../../../powerquery-parser/common/trace"; import { OrderedMap } from "../../../../powerquery-parser"; +import { Assert } from "../../../../powerquery-parser/common"; interface AbridgedType { readonly kind: Type.TypeKind; @@ -137,7 +138,7 @@ describe(`TypeUtils`, () => { expect(simplified.length).to.equal(1); - const actual: AbridgedType = typeToAbridged(simplified[0]); + const actual: AbridgedType = typeToAbridged(Assert.asDefined(simplified[0])); const expected: AbridgedType = TypeUtils.primitiveType(false, Type.TypeKind.Record); expect(actual).deep.equal(expected); }); @@ -209,7 +210,7 @@ describe(`TypeUtils`, () => { expect(simplified.length).to.equal(1); - const actual: AbridgedType = typeToAbridged(simplified[0]); + const actual: AbridgedType = typeToAbridged(Assert.asDefined(simplified[0])); const expected: AbridgedType = typeToAbridged(Type.AnyInstance); expect(actual).deep.equal(expected); }); diff --git a/src/test/libraryTest/lexer/lexError.test.ts b/src/test/libraryTest/lexer/lexError.test.ts index dea5ebd8..16e403fd 100644 --- a/src/test/libraryTest/lexer/lexError.test.ts +++ b/src/test/libraryTest/lexer/lexError.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Lexer, ResultUtils } from "../../.."; +import { Assert } from "../../../powerquery-parser/common"; function assertBadLineNumberKind(lineNumber: number, expectedKind: Lexer.LexError.BadLineNumberKind): void { const triedLex: Lexer.TriedLex = Lexer.tryLex(DefaultSettings, `foo`); @@ -36,7 +37,7 @@ function assertExpectedKind(text: string, expectedKind: Lexer.LexError.ExpectedK const state: Lexer.State = triedLex.value; expect(state.lines.length).to.equal(1); - const line: Lexer.TLine = state.lines[0]; + const line: Lexer.TLine = Assert.asDefined(state.lines[0]); if (!Lexer.isErrorLine(line)) { throw new Error(`AssertFailed: Lexer.isErrorLine(line): ${JSON.stringify(line)}`); diff --git a/src/test/libraryTest/lexer/lexIncremental.test.ts b/src/test/libraryTest/lexer/lexIncremental.test.ts index a5955ee6..6cfeee36 100644 --- a/src/test/libraryTest/lexer/lexIncremental.test.ts +++ b/src/test/libraryTest/lexer/lexIncremental.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { Lexer, ResultUtils } from "../../.."; +import { Assert } from "../../../powerquery-parser/common"; import { assertGetLexOk } from "../../testUtils/lexTestUtils"; const LINE_TERMINATOR: string = `\n`; @@ -81,7 +82,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("Xfoobar"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("Xfoobar"); }); it(`foobar -> fooXbar`, () => { @@ -98,7 +99,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("fooXbar"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("fooXbar"); }); it(`foobar -> Xoobar`, () => { @@ -115,7 +116,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("Xoobar"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("Xoobar"); }); it(`foobar -> X`, () => { @@ -132,7 +133,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("X"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("X"); }); it(`foo\\nbar -> X`, () => { @@ -149,7 +150,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("X"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("X"); }); it(`foo\\nbar -> fXr`, () => { @@ -166,7 +167,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar`, "X", range); expect(state.lines.length).to.equal(1); - expect(state.lines[0].text).to.equal("fXr"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("fXr"); }); it(`foo\\nbar\\baz -> foo\\nX\\nbaz`, () => { @@ -183,9 +184,9 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar\nbaz`, "X", range); expect(state.lines.length).to.equal(3); - expect(state.lines[0].text).to.equal("foo"); - expect(state.lines[1].text).to.equal("X"); - expect(state.lines[2].text).to.equal("baz"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("foo"); + expect(Assert.asDefined(state.lines[1]).text).to.equal("X"); + expect(Assert.asDefined(state.lines[2]).text).to.equal("baz"); }); it(`foo\\nbar\\baz -> foo\\nbXr\\nbaz`, () => { @@ -202,9 +203,9 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar\nbaz`, "X", range); expect(state.lines.length).to.equal(3); - expect(state.lines[0].text).to.equal("foo"); - expect(state.lines[1].text).to.equal("bXr"); - expect(state.lines[2].text).to.equal("baz"); + expect(Assert.asDefined(state.lines[0]).text).to.equal("foo"); + expect(Assert.asDefined(state.lines[1]).text).to.equal("bXr"); + expect(Assert.asDefined(state.lines[2]).text).to.equal("baz"); }); it(`lineTerminator maintained on single line change`, () => { diff --git a/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts b/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts index c0635698..5bea2d79 100644 --- a/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts +++ b/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Language, Lexer, ResultUtils, StringUtils } from "../../.."; +import { Assert } from "../../../powerquery-parser/common"; function assertGetLexerSnapshot(text: string): Lexer.LexerSnapshot { const triedLex: Lexer.TriedLex = Lexer.tryLex(DefaultSettings, text); @@ -80,7 +81,7 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { describe("cache hit consistency", () => { it("repeated calls return identical results", () => { const snapshot: Lexer.LexerSnapshot = assertGetLexerSnapshot("let x = 1"); - const token: Language.Token.Token = snapshot.tokens[0]; + const token: Language.Token.Token = Assert.asDefined(snapshot.tokens[0]); const first: StringUtils.GraphemePosition = snapshot.graphemePositionStartFrom(token); const second: StringUtils.GraphemePosition = snapshot.graphemePositionStartFrom(token); @@ -104,7 +105,7 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { // Column numbers should be increasing for (let i: number = 1; i < positions.length; i += 1) { - expect(positions[i].columnNumber).to.be.greaterThan(positions[i - 1].columnNumber); + expect(Assert.asDefined(positions[i]).columnNumber).to.be.greaterThan(Assert.asDefined(positions[i - 1]).columnNumber); } }); @@ -116,10 +117,10 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { snapshot.graphemePositionStartFrom(token), ); - expect(positions[0].lineNumber).to.equal(0); - expect(positions[1].lineNumber).to.equal(1); - expect(positions[2].lineNumber).to.equal(2); - expect(positions[3].lineNumber).to.equal(2); + expect(Assert.asDefined(positions[0]).lineNumber).to.equal(0); + expect(Assert.asDefined(positions[1]).lineNumber).to.equal(1); + expect(Assert.asDefined(positions[2]).lineNumber).to.equal(2); + expect(Assert.asDefined(positions[3]).lineNumber).to.equal(2); }); }); }); diff --git a/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts b/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts index c4613b4d..90cc0945 100644 --- a/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts +++ b/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { Lexer, ResultUtils } from "../../.."; +import { Assert } from "../../../powerquery-parser/common"; import { assertGetLexOk } from "../../testUtils/lexTestUtils"; describe("Lexer.retokenizeLines line numbers", () => { @@ -30,9 +31,9 @@ describe("Lexer.retokenizeLines line numbers", () => { expect(updated.lines.length).to.equal(3, "expected 3 lines after update"); // All lines should now be in Default mode since line 0 is a complete string - expect(updated.lines[0].lineModeEnd).to.equal(Lexer.LineMode.Default, "line 0 should end in Default mode"); - expect(updated.lines[1].lineModeStart).to.equal(Lexer.LineMode.Default, "line 1 should start in Default mode"); - expect(updated.lines[2].lineModeStart).to.equal(Lexer.LineMode.Default, "line 2 should start in Default mode"); + expect(Assert.asDefined(updated.lines[0]).lineModeEnd).to.equal(Lexer.LineMode.Default, "line 0 should end in Default mode"); + expect(Assert.asDefined(updated.lines[1]).lineModeStart).to.equal(Lexer.LineMode.Default, "line 1 should start in Default mode"); + expect(Assert.asDefined(updated.lines[2]).lineModeStart).to.equal(Lexer.LineMode.Default, "line 2 should start in Default mode"); // Now snapshot to get token positions and verify line numbers are correct. const triedSnapshot: Lexer.TriedLexerSnapshot = Lexer.trySnapshot(updated); diff --git a/src/test/libraryTest/parser/identifierContextKind.test.ts b/src/test/libraryTest/parser/identifierContextKind.test.ts index 2facda06..dc89a169 100644 --- a/src/test/libraryTest/parser/identifierContextKind.test.ts +++ b/src/test/libraryTest/parser/identifierContextKind.test.ts @@ -4,6 +4,7 @@ import "mocha"; import { expect } from "chai"; +import { Assert } from "../../../powerquery-parser/common"; import { NodeIdMap, NodeIdMapUtils, ParseOk } from "../../../powerquery-parser/parser"; import { AssertTestUtils } from "../../testUtils"; import { Ast } from "../../../powerquery-parser/language"; @@ -28,7 +29,7 @@ function assertGetIdentifierByLiteral(parseOk: ParseOk, identifierLiteral: strin if (matches.length === 0) { throw new Error(`could not find the following identifier in the ast: ${identifierLiteral}`); } else if (matches.length === 1) { - return matches[0]; + return Assert.asDefined(matches[0]); } else { throw new Error(`found multiple instances of the following identifier: ${identifierLiteral}`); } diff --git a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts index aaabe790..ce3429df 100644 --- a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts +++ b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts @@ -46,11 +46,11 @@ describe("nodeIdMapIterator", () => { expect(fieldSpecificationKeyValuePairs.length).to.equal(2); - const firstKeyValuePair: FieldSpecificationKeyValuePair = fieldSpecificationKeyValuePairs[0]; + const firstKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[0]); expect(firstKeyValuePair.optional).to.equal(undefined); expect(firstKeyValuePair.normalizedKeyLiteral).to.equal("foo"); - const secondKeyValuePair: FieldSpecificationKeyValuePair = fieldSpecificationKeyValuePairs[1]; + const secondKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[1]); expect(Boolean(secondKeyValuePair.optional)).to.equal(true); expect(secondKeyValuePair.normalizedKeyLiteral).to.equal("bar"); }); @@ -82,12 +82,12 @@ describe("nodeIdMapIterator", () => { expect(parameters.length).to.equal(2); const firstParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(parameters[0]), + XorNodeUtils.assertAst(Assert.asDefined(parameters[0])), Ast.NodeKind.Parameter, ); const secondParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(parameters[1]), + XorNodeUtils.assertAst(Assert.asDefined(parameters[1])), Ast.NodeKind.Parameter, ); @@ -121,12 +121,12 @@ describe("nodeIdMapIterator", () => { expect(parameters.length).to.equal(2); const firstParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(parameters[0]), + XorNodeUtils.assertAst(Assert.asDefined(parameters[0])), Ast.NodeKind.Parameter, ); const secondParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(parameters[1]), + XorNodeUtils.assertAst(Assert.asDefined(parameters[1])), Ast.NodeKind.Parameter, ); @@ -211,7 +211,7 @@ describe("nodeIdMapIterator", () => { expect(recordKeyValuePairs.length).to.equal(1); - const keyValuePair: RecordKeyValuePair = recordKeyValuePairs[0]; + const keyValuePair: RecordKeyValuePair = Assert.asDefined(recordKeyValuePairs[0]); expect(keyValuePair.normalizedKeyLiteral).to.equal("foo"); }); }); @@ -235,11 +235,11 @@ describe("nodeIdMapIterator", () => { expect(fieldSpecificationKeyValuePairs.length).to.equal(2); - const firstKeyValuePair: FieldSpecificationKeyValuePair = fieldSpecificationKeyValuePairs[0]; + const firstKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[0]); expect(firstKeyValuePair.optional).to.equal(undefined); expect(firstKeyValuePair.normalizedKeyLiteral).to.equal("foo"); - const secondKeyValuePair: FieldSpecificationKeyValuePair = fieldSpecificationKeyValuePairs[1]; + const secondKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[1]); expect(Boolean(secondKeyValuePair.optional)).to.equal(true); expect(secondKeyValuePair.normalizedKeyLiteral).to.equal("bar"); }); diff --git a/src/test/libraryTest/parser/typeDirective.test.ts b/src/test/libraryTest/parser/typeDirective.test.ts index 5beaec5b..9ddaf5d0 100644 --- a/src/test/libraryTest/parser/typeDirective.test.ts +++ b/src/test/libraryTest/parser/typeDirective.test.ts @@ -6,6 +6,7 @@ import { expect } from "chai"; import * as AssertTestUtils from "../../testUtils/assertTestUtils"; import { DefaultSettings, Language } from "../../../powerquery-parser"; +import { Assert } from "../../../powerquery-parser/common"; type ParseOk = Awaited>; @@ -21,7 +22,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = letExpression.variableList.elements[0].node; + const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; expect(variable.precedingDirectives).to.equal(undefined); }); @@ -40,7 +41,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = letExpression.variableList.elements[0].node; + const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; expect(variable.precedingDirectives).to.not.equal(undefined); @@ -61,7 +62,7 @@ shared Value = [];`, ); const section: Language.Ast.Section = parseOk.ast as Language.Ast.Section; - const sectionMember: Language.Ast.SectionMember = section.sectionMembers.elements[0]; + const sectionMember: Language.Ast.SectionMember = Assert.asDefined(section.sectionMembers.elements[0]); expect( sectionMember.precedingDirectives?.map((directive: Language.Comment.TDirective) => directive.value), @@ -82,7 +83,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = letExpression.variableList.elements[0].node; + const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; expect( variable.precedingDirectives?.map((directive: Language.Comment.TDirective) => directive.value), @@ -104,7 +105,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = letExpression.variableList.elements[0].node; + const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; expect(variable.precedingDirectives).to.equal(undefined); }); diff --git a/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts b/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts index 1a34288e..abe64eff 100644 --- a/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts +++ b/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Lexer, ResultUtils } from "../../.."; +import { Assert } from "../../../powerquery-parser/common"; import { ILineTokens, IState, IToken, Tokenizer } from "../../testUtils/tokenizerTestUtils"; const tokenizer: Tokenizer = new Tokenizer("\n"); @@ -74,16 +75,18 @@ class MockDocument { if (startingIndex === 0 || this.lineEndStates[startingIndex - 1] === undefined) { state = this.tokenizer.getInitialState(); } else { - state = this.lineEndStates[startingIndex - 1]; + state = Assert.asDefined(this.lineEndStates[startingIndex - 1]); } for (let index: number = startingIndex; index < this.lines.length; index += 1) { - const result: ILineTokens = tokenizer.tokenize(this.lines[index], state); + const result: ILineTokens = tokenizer.tokenize(Assert.asDefined(this.lines[index]), state); this.lineTokens[index] = result.tokens; tokenizedLineCount += 1; // If the new end state matches the previous state, we can stop tokenizing - if (result.endState.equals(this.lineEndStates[index])) { + const previousEndState: IState | undefined = this.lineEndStates[index]; + + if (previousEndState !== undefined && result.endState.equals(previousEndState)) { break; } @@ -158,14 +161,14 @@ describe("MockDocument validation", () => { describe("Incremental updates", () => { it("Re-parse with no change", () => { const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const originalLine: string = document.lines[2]; + const originalLine: string = Assert.asDefined(document.lines[2]); const count: number = document.applyChangeAndTokenize(originalLine, 2); expect(count).equals(1, "we should not have tokenized more than one line"); }); it("Re-parse with simple change", () => { const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = document.lines[2].replace("source", "source123"); + const modified: string = Assert.asDefined(document.lines[2]).replace("source", "source123"); const count: number = document.applyChangeAndTokenize(modified, 2); expect(count).equals(1, "we should not have tokenized more than one line"); }); @@ -173,12 +176,12 @@ describe("Incremental updates", () => { it("Re-parse with unterminated string", () => { const lineNumber: number = 4; const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = document.lines[lineNumber].replace(`"text",`, `"text`); + const modified: string = Assert.asDefined(document.lines[lineNumber]).replace(`"text",`, `"text`); const count: number = document.applyChangeAndTokenize(modified, lineNumber); expect(count).equals(document.lines.length - lineNumber, "remaining lines should have been tokenized"); for (let index: number = lineNumber + 1; index < document.lineTokens.length; index += 1) { - const lineTokens: ReadonlyArray = document.lineTokens[index]; + const lineTokens: ReadonlyArray = Assert.asDefined(document.lineTokens[index]); lineTokens.forEach((token: IToken) => { expect(token.scopes).equals("TextContent", "expecting remaining tokens to be strings"); @@ -189,12 +192,12 @@ describe("Incremental updates", () => { it("Re-parse with unterminated block comment", () => { const lineNumber: number = 3; const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = document.lines[lineNumber].replace(`rce),`, `rce), /* my open comment`); + const modified: string = Assert.asDefined(document.lines[lineNumber]).replace(`rce),`, `rce), /* my open comment`); const count: number = document.applyChangeAndTokenize(modified, lineNumber); expect(count).equals(document.lines.length - lineNumber, "remaining lines should have been tokenized"); for (let index: number = lineNumber + 1; index < document.lineTokens.length; index += 1) { - const lineTokens: ReadonlyArray = document.lineTokens[index]; + const lineTokens: ReadonlyArray = Assert.asDefined(document.lineTokens[index]); lineTokens.forEach((token: IToken) => { expect(token.scopes).equals("MultilineCommentContent", "expecting remaining tokens to be comments"); diff --git a/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts b/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts index 1f27a07a..f278db6d 100644 --- a/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts +++ b/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts @@ -5,6 +5,7 @@ import "mocha"; import { expect } from "chai"; import { ILineTokens, IState, IToken, Tokenizer, TokenizerState } from "../../testUtils/tokenizerTestUtils"; +import { Assert } from "../../../powerquery-parser/common"; const tokenizer: Tokenizer = new Tokenizer(`\n`); const initialState: TokenizerState = tokenizer.getInitialState() as TokenizerState; @@ -16,14 +17,14 @@ function tokenizeLines(query: string, expectedTokenCounts: number[]): void { expect(lines.length).equals(expectedTokenCounts.length); for (let index: number = 0; index < lines.length; index += 1) { - const r: ILineTokens = tokenizer.tokenize(lines[index], state); + const r: ILineTokens = tokenizer.tokenize(Assert.asDefined(lines[index]), state); expect(!state.equals(r.endState), `state should have changed.`); - expect(r.tokens.length).equals(expectedTokenCounts[index], `unexpected token count`); + expect(r.tokens.length).equals(Assert.asDefined(expectedTokenCounts[index]), `unexpected token count`); state = r.endState as TokenizerState; r.tokens.forEach((token: IToken) => { - expect(token.startIndex).is.lessThan(lines[index].length); + expect(token.startIndex).is.lessThan(Assert.asDefined(lines[index]).length); }); } } diff --git a/src/test/testUtils/tokenizerTestUtils.ts b/src/test/testUtils/tokenizerTestUtils.ts index 35ddb0cc..cb079395 100644 --- a/src/test/testUtils/tokenizerTestUtils.ts +++ b/src/test/testUtils/tokenizerTestUtils.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +import { Assert } from "../../powerquery-parser/common"; import { DefaultLocale, Language, ResultUtils } from "../../powerquery-parser"; import { Lexer } from "../.."; @@ -49,7 +50,7 @@ export class Tokenizer implements TokensProvider { const newLexerState: Lexer.State = triedLex.value; return { - tokens: newLexerState.lines[newLexerState.lines.length - 1].tokens.map(Tokenizer.ITokenFrom), + tokens: Assert.asDefined(newLexerState.lines[newLexerState.lines.length - 1]).tokens.map(Tokenizer.ITokenFrom), endState: new TokenizerState(newLexerState), }; } @@ -82,8 +83,8 @@ export class TokenizerState implements IState { } // Compare last line state. - const leftLastLine: Lexer.TLine = this.lexerState.lines[this.lexerState.lines.length - 1]; - const rightLastLine: Lexer.TLine = rightLexerState.lines[rightLexerState.lines.length - 1]; + const leftLastLine: Lexer.TLine = Assert.asDefined(this.lexerState.lines[this.lexerState.lines.length - 1]); + const rightLastLine: Lexer.TLine = Assert.asDefined(rightLexerState.lines[rightLexerState.lines.length - 1]); return leftLastLine.lineModeEnd === rightLastLine.lineModeEnd; } diff --git a/tsconfig.json b/tsconfig.json index 420611f3..190674bd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "lib", From cb93b7462fb407e0f0a027ece3aa1ab286710dd1 Mon Sep 17 00:00:00 2001 From: "Jordan Bolton (jobolton)" Date: Mon, 8 Jun 2026 13:54:56 -0500 Subject: [PATCH 2/3] Enable noUncheckedIndexedAccess and fix all errors with ArrayUtils.assertGet Adds noUncheckedIndexedAccess: true to tsconfig.json and wraps all indexed array accesses with ArrayUtils.assertGet() to satisfy the stricter type checking. This provides runtime safety for all array index accesses that were previously unchecked. Updates ArrayUtils.assertGet to include a default error message and details (index + collection length) for better diagnosability. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- package-lock.json | 4 +-- package.json | 2 +- src/powerquery-parser/common/arrayUtils.ts | 6 +++- src/powerquery-parser/common/stringUtils.ts | 8 +++++ .../language/type/typeUtils/factories.ts | 4 +-- .../language/type/typeUtils/isCompatible.ts | 4 +-- .../language/type/typeUtils/isEqualType.ts | 10 +++--- .../language/type/typeUtils/typeCheck.ts | 8 ++--- .../language/type/typeUtils/typeUtils.ts | 4 +-- src/powerquery-parser/lexer/lexer.ts | 34 +++++++++---------- src/powerquery-parser/lexer/lexerSnapshot.ts | 4 +-- .../parser/context/contextUtils.ts | 2 +- .../disambiguation/disambiguationUtils.ts | 6 ++-- .../parser/nodeIdMap/nodeIdMapIterator.ts | 4 +-- .../nodeIdMap/nodeIdMapUtils/idUtils.ts | 6 ++-- .../nodeIdMap/nodeIdMapUtils/leafSelectors.ts | 4 +-- .../nodeIdMapUtils/nodeIdMapUtils.ts | 4 +-- .../parser/parseState/parseStateUtils.ts | 4 +-- .../parser/parsers/naiveParseSteps.ts | 14 ++++---- .../language/typeUtils/typeCheck.test.ts | 12 +++---- .../language/typeUtils/typeUtils.test.ts | 6 ++-- src/test/libraryTest/lexer/lexError.test.ts | 4 +-- .../libraryTest/lexer/lexIncremental.test.ts | 26 +++++++------- .../lexer/lexerSnapshotCache.test.ts | 14 ++++---- .../lexer/retokenizeLineNumbers.test.ts | 8 ++--- .../parser/identifierContextKind.test.ts | 4 +-- .../parser/parseNodeIdMapUtils.test.ts | 34 +++++++++---------- .../libraryTest/parser/typeDirective.test.ts | 12 +++---- .../tokenizer/tokenizerIncremental.test.ts | 18 +++++----- .../tokenizer/tokenizerSimple.test.ts | 8 ++--- src/test/testUtils/tokenizerTestUtils.ts | 8 ++--- 31 files changed, 149 insertions(+), 137 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33a944dd..1a8eeb18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.19.0", + "version": "0.19.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-parser", - "version": "0.19.0", + "version": "0.19.1", "license": "MIT", "dependencies": { "grapheme-splitter": "^1.0.4", diff --git a/package.json b/package.json index 508067bb..791a1dc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.19.0", + "version": "0.19.1", "description": "A parser for the Power Query/M formula language.", "author": "Microsoft", "license": "MIT", diff --git a/src/powerquery-parser/common/arrayUtils.ts b/src/powerquery-parser/common/arrayUtils.ts index c6fce9ef..cef69e1d 100644 --- a/src/powerquery-parser/common/arrayUtils.ts +++ b/src/powerquery-parser/common/arrayUtils.ts @@ -17,7 +17,11 @@ export function all( } export function assertGet(collection: ReadonlyArray, index: number, message?: string, details?: object): T { - return Assert.asDefined(collection[index], message, details); + return Assert.asDefined( + collection[index], + message ?? "index out of bounds", + details ?? { index, collectionLength: collection.length }, + ); } export function assertIncludes(collection: ReadonlyArray, element: T, message?: string, details?: object): void { diff --git a/src/powerquery-parser/common/stringUtils.ts b/src/powerquery-parser/common/stringUtils.ts index d1cfefcd..4bf5dcea 100644 --- a/src/powerquery-parser/common/stringUtils.ts +++ b/src/powerquery-parser/common/stringUtils.ts @@ -5,6 +5,14 @@ import GraphemeSplitter = require("grapheme-splitter"); import { Assert, CommonError, Pattern } from "."; +export function assertGet(text: string, index: number, message?: string, details?: object): string { + return Assert.asDefined( + text[index], + message ?? "index out of bounds", + details ?? { index, textLength: text.length }, + ); +} + export const graphemeSplitter: GraphemeSplitter = new GraphemeSplitter(); export interface FoundQuotes { diff --git a/src/powerquery-parser/language/type/typeUtils/factories.ts b/src/powerquery-parser/language/type/typeUtils/factories.ts index 09acb834..9403ba36 100644 --- a/src/powerquery-parser/language/type/typeUtils/factories.ts +++ b/src/powerquery-parser/language/type/typeUtils/factories.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, CommonError, StringUtils } from "../../../common"; +import { ArrayUtils, Assert, CommonError, StringUtils } from "../../../common"; import { PrimitiveTypeConstantMap, primitiveTypeMapKey } from "./primitive"; import { Trace, TraceManager } from "../../../common/trace"; import { simplify } from "./simplify"; @@ -31,7 +31,7 @@ export function anyUnion( if (simplified.length === 1) { trace.exit(); - return Assert.asDefined(simplified[0]); + return ArrayUtils.assertGet(simplified, 0); } const result: Type.AnyUnion = { diff --git a/src/powerquery-parser/language/type/typeUtils/isCompatible.ts b/src/powerquery-parser/language/type/typeUtils/isCompatible.ts index 315bf82a..09821c55 100644 --- a/src/powerquery-parser/language/type/typeUtils/isCompatible.ts +++ b/src/powerquery-parser/language/type/typeUtils/isCompatible.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, CommonError, MapUtils } from "../../../common"; +import { ArrayUtils, Assert, CommonError, MapUtils } from "../../../common"; import { isEqualFunctionSignature, isEqualType } from "./isEqualType"; import { isFieldSpecificationList, isFunctionSignature } from "./isType"; import { Trace, TraceManager } from "../../../common/trace"; @@ -777,7 +777,7 @@ function isCompatibleDefinedListOrDefinedListType - isEqualType(leftType, Assert.asDefined(rightElements[index])), + isEqualType(leftType, ArrayUtils.assertGet(rightElements, index)), ), ); } @@ -160,7 +160,7 @@ export function isEqualDefinedListType(left: Type.DefinedListType, right: Type.D return ArrayUtils.all( left.itemTypes.map((leftType: Type.TPowerQueryType, index: number) => - isEqualType(leftType, Assert.asDefined(rightElements[index])), + isEqualType(leftType, ArrayUtils.assertGet(rightElements, index)), ), ); } @@ -224,8 +224,8 @@ export function isEqualFunctionParameters( const numParameters: number = left.length; for (let index: number = 0; index < numParameters; index += 1) { - const nthLeft: Type.FunctionParameter = Assert.asDefined(left[index]); - const nthRight: Type.FunctionParameter = Assert.asDefined(right[index]); + const nthLeft: Type.FunctionParameter = ArrayUtils.assertGet(left, index); + const nthRight: Type.FunctionParameter = ArrayUtils.assertGet(right, index); if (!isEqualFunctionParameter(nthLeft, nthRight)) { return false; diff --git a/src/powerquery-parser/language/type/typeUtils/typeCheck.ts b/src/powerquery-parser/language/type/typeUtils/typeCheck.ts index f0217f54..231d1189 100644 --- a/src/powerquery-parser/language/type/typeUtils/typeCheck.ts +++ b/src/powerquery-parser/language/type/typeUtils/typeCheck.ts @@ -3,7 +3,7 @@ import { isCompatible, isCompatibleWithFunctionParameter } from "./isCompatible"; import { Trace, TraceManager } from "../../../common/trace"; -import { ArrayUtils, Assert } from "../../../common"; +import { ArrayUtils } from "../../../common"; import { isEqualFunctionParameter } from "./isEqualType"; import { Type } from ".."; import { TypeUtilsTraceConstant } from "./typeTraceConstant"; @@ -127,7 +127,7 @@ export function typeCheckInvocation( for (let index: number = 0; index < numParameters; index += 1) { const arg: Type.TPowerQueryType | undefined = args[index]; - const parameter: Type.FunctionParameter = Assert.asDefined(parameters[index]); + const parameter: Type.FunctionParameter = ArrayUtils.assertGet(parameters, index); if (isCompatibleWithFunctionParameter(arg, parameter)) { validArgs.push(index); @@ -257,8 +257,8 @@ function typeCheckGenericNumber< const mismatches: Map> = new Map(); for (let index: number = 0; index < upperBound; index += 1) { - const element: Value = Assert.asDefined(valueElements[index]); - const schemaItemType: Schema = Assert.asDefined(schemaItemTypes[index]); + const element: Value = ArrayUtils.assertGet(valueElements, index); + const schemaItemType: Schema = ArrayUtils.assertGet(schemaItemTypes, index); if (comparer(element, schemaItemType, traceManager, trace.id)) { validIndices.push(index); diff --git a/src/powerquery-parser/language/type/typeUtils/typeUtils.ts b/src/powerquery-parser/language/type/typeUtils/typeUtils.ts index 4fbbc42f..53c2cfe9 100644 --- a/src/powerquery-parser/language/type/typeUtils/typeUtils.ts +++ b/src/powerquery-parser/language/type/typeUtils/typeUtils.ts @@ -4,7 +4,7 @@ import { Ast, AstUtils } from "../.."; import { NodeIdMap, NodeIdMapUtils, ParseContext, XorNode, XorNodeKind } from "../../../parser"; import { Trace, TraceManager } from "../../../common/trace"; -import { Assert } from "../../../common"; +import { ArrayUtils, Assert } from "../../../common"; import { isCompatible } from "./isCompatible"; import { isEqualType } from "./isEqualType"; import { primitiveType } from "./factories"; @@ -91,7 +91,7 @@ export function isValidInvocation( const numParameters: number = parameters.length; for (let index: number = 1; index < numParameters; index += 1) { - const parameter: Type.FunctionParameter = Assert.asDefined(parameters[index]); + const parameter: Type.FunctionParameter = ArrayUtils.assertGet(parameters, index); const argType: Type.TPowerQueryType | undefined = args[index]; if (argType !== undefined) { diff --git a/src/powerquery-parser/lexer/lexer.ts b/src/powerquery-parser/lexer/lexer.ts index a74b9ad4..74507daf 100644 --- a/src/powerquery-parser/lexer/lexer.ts +++ b/src/powerquery-parser/lexer/lexer.ts @@ -148,8 +148,8 @@ export function equalLines(leftLines: ReadonlyArray, rightLines: Readonly const numLines: number = leftLines.length; for (let lineIndex: number = 0; lineIndex < numLines; lineIndex += 1) { - const left: TLine = Assert.asDefined(leftLines[lineIndex]); - const right: TLine = Assert.asDefined(rightLines[lineIndex]); + const left: TLine = ArrayUtils.assertGet(leftLines, lineIndex); + const right: TLine = ArrayUtils.assertGet(rightLines, lineIndex); const leftTokens: ReadonlyArray = left.tokens; const rightTokens: ReadonlyArray = right.tokens; @@ -168,7 +168,7 @@ export function equalLines(leftLines: ReadonlyArray, rightLines: Readonly const numTokens: number = leftTokens.length; for (let tokenIndex: number = 0; tokenIndex < numTokens; tokenIndex += 1) { - if (!equalTokens(Assert.asDefined(leftTokens[tokenIndex]), Assert.asDefined(rightTokens[tokenIndex]))) { + if (!equalTokens(ArrayUtils.assertGet(leftTokens, tokenIndex), ArrayUtils.assertGet(rightTokens, tokenIndex))) { return false; } } @@ -265,7 +265,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { let indexWasExpanded: boolean = false; for (const lineTerminator of lineTerminators) { - const splitLine: SplitLine = Assert.asDefined(lines[index]); + const splitLine: SplitLine = ArrayUtils.assertGet(lines, index); const text: string = splitLine.text; if (text.indexOf(lineTerminator) !== -1) { @@ -276,7 +276,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { lineTerminator, })); - Assert.asDefined(split[split.length - 1]).lineTerminator = splitLine.lineTerminator; + ArrayUtils.assertGet(split, split.length - 1).lineTerminator = splitLine.lineTerminator; lines = [...lines.slice(0, index), ...split, ...lines.slice(index + 1)]; } @@ -287,7 +287,7 @@ function splitOnLineTerminators(startingText: string): SplitLine[] { } } - Assert.asDefined(lines[lines.length - 1]).lineTerminator = ""; + ArrayUtils.assertGet(lines, lines.length - 1).lineTerminator = ""; return lines; } @@ -356,7 +356,7 @@ function updateLine(state: State, lineNumber: number, text: string): State { throw error; } - const line: TLine = Assert.asDefined(state.lines[lineNumber]); + const line: TLine = ArrayUtils.assertGet(state.lines, lineNumber); const range: Range = rangeFrom(line, lineNumber); return updateRange(state, range, text); @@ -374,14 +374,14 @@ function updateRange(state: State, range: Range, text: string): State { const splitLines: SplitLine[] = splitOnLineTerminators(text); const rangeStart: RangePosition = range.start; - const lineStart: TLine = Assert.asDefined(state.lines[rangeStart.lineNumber]); + const lineStart: TLine = ArrayUtils.assertGet(state.lines, rangeStart.lineNumber); const textPrefix: string = lineStart.text.substring(0, rangeStart.lineCodeUnit); - Assert.asDefined(splitLines[0]).text = textPrefix + Assert.asDefined(splitLines[0]).text; + ArrayUtils.assertGet(splitLines, 0).text = textPrefix + ArrayUtils.assertGet(splitLines, 0).text; const rangeEnd: RangePosition = range.end; - const lineEnd: TLine = Assert.asDefined(state.lines[rangeEnd.lineNumber]); + const lineEnd: TLine = ArrayUtils.assertGet(state.lines, rangeEnd.lineNumber); const textSuffix: string = lineEnd.text.substr(rangeEnd.lineCodeUnit); - const lastSplitLine: SplitLine = Assert.asDefined(splitLines[splitLines.length - 1]); + const lastSplitLine: SplitLine = ArrayUtils.assertGet(splitLines, splitLines.length - 1); lastSplitLine.text = lastSplitLine.text + textSuffix; // make sure we have a line terminator @@ -400,7 +400,7 @@ function updateRange(state: State, range: Range, text: string): State { const lines: ReadonlyArray = [ ...state.lines.slice(0, rangeStart.lineNumber), ...newLines, - ...retokenizeLines(state, rangeEnd.lineNumber + 1, Assert.asDefined(newLines[newLines.length - 1]).lineModeEnd), + ...retokenizeLines(state, rangeEnd.lineNumber + 1, ArrayUtils.assertGet(newLines, newLines.length - 1).lineModeEnd), ]; return { @@ -485,7 +485,7 @@ function retokenizeLines(state: State, lineNumber: number, previousLineModeEnd: const retokenizedLines: TLine[] = []; - if (previousLineModeEnd !== Assert.asDefined(lines[lineNumber]).lineModeStart) { + if (previousLineModeEnd !== ArrayUtils.assertGet(lines, lineNumber).lineModeStart) { let currentLine: TLine | undefined = lines[lineNumber]; while (currentLine) { @@ -793,7 +793,7 @@ function tokenizeTextLiteralContentOrEnd(line: TLine, currentPosition: number): function tokenizeDefault(line: TLine, lineNumber: number, positionStart: number, locale: string): LineModeAlteringRead { const text: string = line.text; - const chr1: string = Assert.asDefined(text[positionStart]); + const chr1: string = StringUtils.assertGet(text, positionStart); let token: Token.LineToken; let lineMode: LineMode = LineMode.Default; @@ -855,7 +855,7 @@ function tokenizeDefault(line: TLine, lineNumber: number, positionStart: number, } else if ("1" <= chr2 && chr2 <= "9") { token = readNumericLiteral(text, lineNumber, positionStart, locale); } else if (chr2 === ".") { - const chr3: string = Assert.asDefined(text[positionStart + 2]); + const chr3: string = StringUtils.assertGet(text, positionStart + 2); if (chr3 === ".") { token = readConstant(Token.LineTokenKind.Ellipsis, text, positionStart, 3); @@ -1321,8 +1321,8 @@ function testBadRangeError(state: State, range: Range): LexError.BadRangeError | const rangeStart: RangePosition = range.start; const rangeEnd: RangePosition = range.end; - const lineStart: TLine = Assert.asDefined(lines[rangeStart.lineNumber]); - const lineEnd: TLine = Assert.asDefined(lines[rangeEnd.lineNumber]); + const lineStart: TLine = ArrayUtils.assertGet(lines, rangeStart.lineNumber); + const lineEnd: TLine = ArrayUtils.assertGet(lines, rangeEnd.lineNumber); if (rangeStart.lineCodeUnit > lineStart.text.length) { kind = LexError.BadRangeKind.LineCodeUnitStart_GreaterThan_LineLength; diff --git a/src/powerquery-parser/lexer/lexerSnapshot.ts b/src/powerquery-parser/lexer/lexerSnapshot.ts index e5a07d29..81c95298 100644 --- a/src/powerquery-parser/lexer/lexerSnapshot.ts +++ b/src/powerquery-parser/lexer/lexerSnapshot.ts @@ -135,7 +135,7 @@ function createSnapshot(state: Lexer.State): LexerSnapshot { while (flatIndex < numFlatTokens) { state.cancellationToken?.throwIfCancelled(); - const flatToken: FlatLineToken = Assert.asDefined(flatTokens[flatIndex]); + const flatToken: FlatLineToken = ArrayUtils.assertGet(flatTokens, flatIndex); const lineTokenKind: Token.LineTokenKind = flatToken.kind; switch (lineTokenKind) { @@ -553,7 +553,7 @@ function collectWhileContent( while (flatIndex < numTokens) { cancellationToken?.throwIfCancelled(); - const token: FlatLineToken = Assert.asDefined(flatTokens[flatIndex]); + const token: FlatLineToken = ArrayUtils.assertGet(flatTokens, flatIndex); if (token.kind !== contentKind) { break; diff --git a/src/powerquery-parser/parser/context/contextUtils.ts b/src/powerquery-parser/parser/context/contextUtils.ts index 97252604..ada19796 100644 --- a/src/powerquery-parser/parser/context/contextUtils.ts +++ b/src/powerquery-parser/parser/context/contextUtils.ts @@ -335,7 +335,7 @@ export function deleteContext(state: ParseContext.State, nodeId: number): ParseC // Not a leaf node. if (childIds !== undefined) { ArrayUtils.assertNonZeroLength(childIds); - const childId: number = Assert.asDefined(childIds[0]); + const childId: number = ArrayUtils.assertGet(childIds, 0); // Not a leaf node, is the Root node. // Promote the child to the root if it's a Context node. diff --git a/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts b/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts index 74b5fd18..51acc1d2 100644 --- a/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts +++ b/src/powerquery-parser/parser/disambiguation/disambiguationUtils.ts @@ -213,7 +213,7 @@ export async function disambiguateParenthesis( let offsetTokenIndex: number = initialTokenIndex + 1; while (offsetTokenIndex < totalTokens) { - const offsetTokenKind: Token.TokenKind = Assert.asDefined(tokens[offsetTokenIndex]).kind; + const offsetTokenKind: Token.TokenKind = ArrayUtils.assertGet(tokens, offsetTokenIndex).kind; if (offsetTokenKind === Token.TokenKind.LeftParenthesis) { nestedDepth += 1; @@ -305,7 +305,7 @@ export function disambiguateBracket( offsetTokenIndex += 1; while (offsetTokenIndex < totalTokens) { - offsetTokenKind = Assert.asDefined(tokens[offsetTokenIndex]).kind; + offsetTokenKind = ArrayUtils.assertGet(tokens, offsetTokenIndex).kind; if (offsetTokenKind === Token.TokenKind.Equal) { result = BracketDisambiguation.RecordExpression; @@ -472,7 +472,7 @@ function unsafeMoveTo(state: ParseState, tokenIndex: number): void { state.tokenIndex = tokenIndex; if (tokenIndex < tokens.length) { - state.currentToken = Assert.asDefined(tokens[tokenIndex]); + state.currentToken = ArrayUtils.assertGet(tokens, tokenIndex); state.currentTokenKind = state.currentToken.kind; } else { state.currentToken = undefined; diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts index 9ddc467d..8ef64687 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapIterator.ts @@ -3,7 +3,7 @@ import { Ast, Constant, IdentifierUtils } from "../../language"; import { NodeIdMap, NodeIdMapUtils, TXorNode, XorNodeKind, XorNodeUtils } from "."; -import { Assert } from "../../common"; +import { ArrayUtils, Assert } from "../../common"; import { parameterIdentifier } from "./nodeIdMapUtils"; import { XorNode } from "./xorNode"; @@ -128,7 +128,7 @@ export function nthSiblingXor( return undefined; } - return NodeIdMapUtils.xor(nodeIdMapCollection, Assert.asDefined(childIds[attributeIndex])); + return NodeIdMapUtils.xor(nodeIdMapCollection, ArrayUtils.assertGet(childIds, attributeIndex)); } // ------------------------------------------ diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts index c9011099..ae6fa3ff 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/idUtils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, MapUtils, TypeScriptUtils } from "../../../common"; +import { ArrayUtils, MapUtils, TypeScriptUtils } from "../../../common"; import { Trace, TraceConstant, TraceManager } from "../../../common/trace"; import { Ast } from "../../../language"; import { Collection } from "../nodeIdMap"; @@ -63,8 +63,8 @@ export function recalculateIds( const newIdByOldId: Map = new Map(); for (let index: number = 0; index < numIds; index += 1) { - const oldId: number = Assert.asDefined(encounteredIds[index]); - const newId: number = Assert.asDefined(sortedIds[index]); + const oldId: number = ArrayUtils.assertGet(encounteredIds, index); + const newId: number = ArrayUtils.assertGet(sortedIds, index); if (oldId !== newId) { newIdByOldId.set(oldId, newId); diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts index 61ba0124..8fe88b63 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/leafSelectors.ts @@ -3,7 +3,7 @@ import { AstNodeById, Collection } from "../nodeIdMap"; import { NodeIdMap, XorNodeUtils } from ".."; -import { Assert } from "../../../common"; +import { ArrayUtils, Assert } from "../../../common"; import { Ast } from "../../../language"; import { TXorNode } from "../xorNode"; import { xor } from "./commonSelectors"; @@ -35,7 +35,7 @@ export function leftMostXor(nodeIdMapCollection: Collection, nodeId: number): TX let childIds: ReadonlyArray | undefined = nodeIdMapCollection.childIdsById.get(currentNodeId); while (childIds?.length) { - currentNodeId = Assert.asDefined(childIds[0]); + currentNodeId = ArrayUtils.assertGet(childIds, 0); childIds = nodeIdMapCollection.childIdsById.get(currentNodeId); } diff --git a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts index 3a8c4eb5..8c1fe021 100644 --- a/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts +++ b/src/powerquery-parser/parser/nodeIdMap/nodeIdMapUtils/nodeIdMapUtils.ts @@ -4,7 +4,7 @@ import { Ast, Token } from "../../../language"; import { Collection, CollectionValidation, IdsByNodeKind, NodeSummary } from "../nodeIdMap"; import { TXorNode, XorNodeKind, XorNodeTokenRange } from "../xorNode"; -import { Assert } from "../../../common"; +import { ArrayUtils, Assert } from "../../../common"; import { ParseContext } from "../../context"; import { rightMostLeaf } from "./leafSelectors"; @@ -56,7 +56,7 @@ export function hasParsedToken(nodeIdMapCollection: Collection, nodeId: number): } // There might be a child under here. else if (numChildren === 1) { - const childId: number = Assert.asDefined(childIds[0]); + const childId: number = ArrayUtils.assertGet(childIds, 0); // We know it's an Ast Node, therefore something was parsed. if (nodeIdMapCollection.astNodeById.has(childId)) { diff --git a/src/powerquery-parser/parser/parseState/parseStateUtils.ts b/src/powerquery-parser/parser/parseState/parseStateUtils.ts index 175e73b9..435fbf73 100644 --- a/src/powerquery-parser/parser/parseState/parseStateUtils.ts +++ b/src/powerquery-parser/parser/parseState/parseStateUtils.ts @@ -162,7 +162,7 @@ export function isOnTokenKind( export function isOnConstantKind(state: ParseState, constantKind: Constant.TConstant): boolean { if (isOnTokenKind(state, Token.TokenKind.Identifier)) { - const currentToken: Token.Token = Assert.asDefined(state.lexerSnapshot.tokens[state.tokenIndex]); + const currentToken: Token.Token = ArrayUtils.assertGet(state.lexerSnapshot.tokens, state.tokenIndex); if (currentToken?.data === undefined) { const details: { currentToken: Token.Token } = { currentToken }; @@ -283,7 +283,7 @@ export function assertGetContextNodeMetadata(state: ParseState): ContextNodeMeta // inclusive token index const tokenIndexEnd: number = state.tokenIndex - 1; - const tokenEnd: Token.Token = Assert.asDefined(state.lexerSnapshot.tokens[tokenIndexEnd]); + const tokenEnd: Token.Token = ArrayUtils.assertGet(state.lexerSnapshot.tokens, tokenIndexEnd); const tokenRange: Token.TokenRange = { tokenIndexStart: currentContextNode.tokenIndexStart, diff --git a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts index 92b34f2d..a5c543c3 100644 --- a/src/powerquery-parser/parser/parsers/naiveParseSteps.ts +++ b/src/powerquery-parser/parser/parsers/naiveParseSteps.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, CommonError, Result, ResultUtils } from "../../common"; +import { ArrayUtils, Assert, CommonError, Result, ResultUtils } from "../../common"; import { Ast, AstUtils, Comment, Constant, ConstantUtils, IdentifierUtils, Token } from "../../language"; import { Disambiguation, DisambiguationUtils } from "../disambiguation"; import { NaiveParseSteps, ParseError } from ".."; @@ -122,8 +122,8 @@ export async function readGeneralizedIdentifier( const lexerSnapshot: LexerSnapshot = state.lexerSnapshot; const tokens: ReadonlyArray = lexerSnapshot.tokens; - const contiguousIdentifierStartIndex: number = Assert.asDefined(tokens[tokenRangeStartIndex]).positionStart.codeUnit; - const contiguousIdentifierEndIndex: number = Assert.asDefined(tokens[tokenRangeEndIndex - 1]).positionEnd.codeUnit; + const contiguousIdentifierStartIndex: number = ArrayUtils.assertGet(tokens, tokenRangeStartIndex).positionStart.codeUnit; + const contiguousIdentifierEndIndex: number = ArrayUtils.assertGet(tokens, tokenRangeEndIndex - 1).positionEnd.codeUnit; const literal: string = lexerSnapshot.text.slice(contiguousIdentifierStartIndex, contiguousIdentifierEndIndex); const literalKind: IdentifierUtils.IdentifierKind = IdentifierUtils.getIdentifierKind(literal, { @@ -2806,7 +2806,7 @@ async function tryReadPrimitiveType( let primitiveTypeKind: Constant.PrimitiveTypeConstant; if (ParseStateUtils.isOnTokenKind(state, TokenKind.Identifier)) { - const currentTokenData: string = Assert.asDefined(state.lexerSnapshot.tokens[state.tokenIndex]).data; + const currentTokenData: string = ArrayUtils.assertGet(state.lexerSnapshot.tokens, state.tokenIndex).data; switch (currentTokenData) { case Constant.PrimitiveTypeConstant.Action: @@ -3602,7 +3602,7 @@ export function readToken(state: ParseState): string { tokensLength: tokens.length, }); - const data: string = Assert.asDefined(tokens[state.tokenIndex]).data; + const data: string = ArrayUtils.assertGet(tokens, state.tokenIndex).data; state.tokenIndex += 1; if (state.tokenIndex === tokens.length) { @@ -3615,7 +3615,7 @@ export function readToken(state: ParseState): string { // So, for now when a IParseState is Eof when currentTokenKind === undefined. state.currentTokenKind = undefined; } else { - state.currentToken = Assert.asDefined(tokens[state.tokenIndex]); + state.currentToken = ArrayUtils.assertGet(tokens, state.tokenIndex); state.currentTokenKind = state.currentToken.kind; } @@ -3867,7 +3867,7 @@ function testCatchFunction( if ( parameters.length > 1 || - (parameters.length === 1 && Assert.asDefined(parameters[0]).node.parameterType) || + (parameters.length === 1 && ArrayUtils.assertGet(parameters, 0).node.parameterType) || catchFunction.functionReturnType ) { const tokenStart: Token.Token = Assert.asDefined( diff --git a/src/test/libraryTest/language/typeUtils/typeCheck.test.ts b/src/test/libraryTest/language/typeUtils/typeCheck.test.ts index da6da51f..a0d0f04e 100644 --- a/src/test/libraryTest/language/typeUtils/typeCheck.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeCheck.test.ts @@ -7,7 +7,7 @@ import { expect } from "chai"; import { CheckedDefinedList, CheckedInvocation } from "../../../../powerquery-parser/language/type/typeUtils"; import { Type, TypeUtils } from "../../../../powerquery-parser/language"; import { Language } from "../../../.."; -import { Assert } from "../../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../../powerquery-parser/common"; import { NoOpTraceManagerInstance } from "../../../../powerquery-parser/common/trace"; import { OrderedMap } from "../../../../powerquery-parser"; @@ -301,7 +301,7 @@ describe(`TypeUtils.typeCheck`, () => { 0, { actual: args[0], - expected: Assert.asDefined(definedFunction.parameters[0]), + expected: ArrayUtils.assertGet(definedFunction.parameters, 0), }, ], ]), @@ -360,7 +360,7 @@ describe(`TypeUtils.typeCheck`, () => { const expected: TypeUtils.CheckedInvocation = { valid: [], - invalid: new Map([[0, { actual: args[0], expected: Assert.asDefined(definedFunction.parameters[0]) }]]), + invalid: new Map([[0, { actual: args[0], expected: ArrayUtils.assertGet(definedFunction.parameters, 0) }]]), extraneous: [], missing: [], }; @@ -388,7 +388,7 @@ describe(`TypeUtils.typeCheck`, () => { const expected: TypeUtils.CheckedInvocation = { valid: [], - invalid: new Map([[0, { actual: args[0], expected: Assert.asDefined(definedFunction.parameters[0]) }]]), + invalid: new Map([[0, { actual: args[0], expected: ArrayUtils.assertGet(definedFunction.parameters, 0) }]]), extraneous: [], missing: [], }; @@ -430,14 +430,14 @@ describe(`TypeUtils.typeCheck`, () => { 0, { actual: args[0], - expected: Assert.asDefined(definedFunction.parameters[0]), + expected: ArrayUtils.assertGet(definedFunction.parameters, 0), }, ], [ 1, { actual: args[1], - expected: Assert.asDefined(definedFunction.parameters[1]), + expected: ArrayUtils.assertGet(definedFunction.parameters, 1), }, ], ]), diff --git a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts index 056dac8e..2e21c37d 100644 --- a/src/test/libraryTest/language/typeUtils/typeUtils.test.ts +++ b/src/test/libraryTest/language/typeUtils/typeUtils.test.ts @@ -7,7 +7,7 @@ import { expect } from "chai"; import { Type, TypeUtils } from "../../../../powerquery-parser/language"; import { NoOpTraceManagerInstance } from "../../../../powerquery-parser/common/trace"; import { OrderedMap } from "../../../../powerquery-parser"; -import { Assert } from "../../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../../powerquery-parser/common"; interface AbridgedType { readonly kind: Type.TypeKind; @@ -138,7 +138,7 @@ describe(`TypeUtils`, () => { expect(simplified.length).to.equal(1); - const actual: AbridgedType = typeToAbridged(Assert.asDefined(simplified[0])); + const actual: AbridgedType = typeToAbridged(ArrayUtils.assertGet(simplified, 0)); const expected: AbridgedType = TypeUtils.primitiveType(false, Type.TypeKind.Record); expect(actual).deep.equal(expected); }); @@ -210,7 +210,7 @@ describe(`TypeUtils`, () => { expect(simplified.length).to.equal(1); - const actual: AbridgedType = typeToAbridged(Assert.asDefined(simplified[0])); + const actual: AbridgedType = typeToAbridged(ArrayUtils.assertGet(simplified, 0)); const expected: AbridgedType = typeToAbridged(Type.AnyInstance); expect(actual).deep.equal(expected); }); diff --git a/src/test/libraryTest/lexer/lexError.test.ts b/src/test/libraryTest/lexer/lexError.test.ts index 16e403fd..c8e1cd48 100644 --- a/src/test/libraryTest/lexer/lexError.test.ts +++ b/src/test/libraryTest/lexer/lexError.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Lexer, ResultUtils } from "../../.."; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; function assertBadLineNumberKind(lineNumber: number, expectedKind: Lexer.LexError.BadLineNumberKind): void { const triedLex: Lexer.TriedLex = Lexer.tryLex(DefaultSettings, `foo`); @@ -37,7 +37,7 @@ function assertExpectedKind(text: string, expectedKind: Lexer.LexError.ExpectedK const state: Lexer.State = triedLex.value; expect(state.lines.length).to.equal(1); - const line: Lexer.TLine = Assert.asDefined(state.lines[0]); + const line: Lexer.TLine = ArrayUtils.assertGet(state.lines, 0); if (!Lexer.isErrorLine(line)) { throw new Error(`AssertFailed: Lexer.isErrorLine(line): ${JSON.stringify(line)}`); diff --git a/src/test/libraryTest/lexer/lexIncremental.test.ts b/src/test/libraryTest/lexer/lexIncremental.test.ts index 6cfeee36..b0fd6b2e 100644 --- a/src/test/libraryTest/lexer/lexIncremental.test.ts +++ b/src/test/libraryTest/lexer/lexIncremental.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { Lexer, ResultUtils } from "../../.."; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; import { assertGetLexOk } from "../../testUtils/lexTestUtils"; const LINE_TERMINATOR: string = `\n`; @@ -82,7 +82,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("Xfoobar"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("Xfoobar"); }); it(`foobar -> fooXbar`, () => { @@ -99,7 +99,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("fooXbar"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("fooXbar"); }); it(`foobar -> Xoobar`, () => { @@ -116,7 +116,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("Xoobar"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("Xoobar"); }); it(`foobar -> X`, () => { @@ -133,7 +133,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foobar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("X"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("X"); }); it(`foo\\nbar -> X`, () => { @@ -150,7 +150,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("X"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("X"); }); it(`foo\\nbar -> fXr`, () => { @@ -167,7 +167,7 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar`, "X", range); expect(state.lines.length).to.equal(1); - expect(Assert.asDefined(state.lines[0]).text).to.equal("fXr"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("fXr"); }); it(`foo\\nbar\\baz -> foo\\nX\\nbaz`, () => { @@ -184,9 +184,9 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar\nbaz`, "X", range); expect(state.lines.length).to.equal(3); - expect(Assert.asDefined(state.lines[0]).text).to.equal("foo"); - expect(Assert.asDefined(state.lines[1]).text).to.equal("X"); - expect(Assert.asDefined(state.lines[2]).text).to.equal("baz"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("foo"); + expect(ArrayUtils.assertGet(state.lines, 1).text).to.equal("X"); + expect(ArrayUtils.assertGet(state.lines, 2).text).to.equal("baz"); }); it(`foo\\nbar\\baz -> foo\\nbXr\\nbaz`, () => { @@ -203,9 +203,9 @@ describe(`Lexer.Incremental`, () => { const state: Lexer.State = assertGetLexerUpdateRangeOk(`foo\nbar\nbaz`, "X", range); expect(state.lines.length).to.equal(3); - expect(Assert.asDefined(state.lines[0]).text).to.equal("foo"); - expect(Assert.asDefined(state.lines[1]).text).to.equal("bXr"); - expect(Assert.asDefined(state.lines[2]).text).to.equal("baz"); + expect(ArrayUtils.assertGet(state.lines, 0).text).to.equal("foo"); + expect(ArrayUtils.assertGet(state.lines, 1).text).to.equal("bXr"); + expect(ArrayUtils.assertGet(state.lines, 2).text).to.equal("baz"); }); it(`lineTerminator maintained on single line change`, () => { diff --git a/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts b/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts index 5bea2d79..0fe9ef33 100644 --- a/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts +++ b/src/test/libraryTest/lexer/lexerSnapshotCache.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Language, Lexer, ResultUtils, StringUtils } from "../../.."; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; function assertGetLexerSnapshot(text: string): Lexer.LexerSnapshot { const triedLex: Lexer.TriedLex = Lexer.tryLex(DefaultSettings, text); @@ -81,7 +81,7 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { describe("cache hit consistency", () => { it("repeated calls return identical results", () => { const snapshot: Lexer.LexerSnapshot = assertGetLexerSnapshot("let x = 1"); - const token: Language.Token.Token = Assert.asDefined(snapshot.tokens[0]); + const token: Language.Token.Token = ArrayUtils.assertGet(snapshot.tokens, 0); const first: StringUtils.GraphemePosition = snapshot.graphemePositionStartFrom(token); const second: StringUtils.GraphemePosition = snapshot.graphemePositionStartFrom(token); @@ -105,7 +105,7 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { // Column numbers should be increasing for (let i: number = 1; i < positions.length; i += 1) { - expect(Assert.asDefined(positions[i]).columnNumber).to.be.greaterThan(Assert.asDefined(positions[i - 1]).columnNumber); + expect(ArrayUtils.assertGet(positions, i).columnNumber).to.be.greaterThan(ArrayUtils.assertGet(positions, i - 1).columnNumber); } }); @@ -117,10 +117,10 @@ describe("LexerSnapshot.graphemePositionStartFrom cache", () => { snapshot.graphemePositionStartFrom(token), ); - expect(Assert.asDefined(positions[0]).lineNumber).to.equal(0); - expect(Assert.asDefined(positions[1]).lineNumber).to.equal(1); - expect(Assert.asDefined(positions[2]).lineNumber).to.equal(2); - expect(Assert.asDefined(positions[3]).lineNumber).to.equal(2); + expect(ArrayUtils.assertGet(positions, 0).lineNumber).to.equal(0); + expect(ArrayUtils.assertGet(positions, 1).lineNumber).to.equal(1); + expect(ArrayUtils.assertGet(positions, 2).lineNumber).to.equal(2); + expect(ArrayUtils.assertGet(positions, 3).lineNumber).to.equal(2); }); }); }); diff --git a/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts b/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts index 90cc0945..2b8432f3 100644 --- a/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts +++ b/src/test/libraryTest/lexer/retokenizeLineNumbers.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { Lexer, ResultUtils } from "../../.."; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; import { assertGetLexOk } from "../../testUtils/lexTestUtils"; describe("Lexer.retokenizeLines line numbers", () => { @@ -31,9 +31,9 @@ describe("Lexer.retokenizeLines line numbers", () => { expect(updated.lines.length).to.equal(3, "expected 3 lines after update"); // All lines should now be in Default mode since line 0 is a complete string - expect(Assert.asDefined(updated.lines[0]).lineModeEnd).to.equal(Lexer.LineMode.Default, "line 0 should end in Default mode"); - expect(Assert.asDefined(updated.lines[1]).lineModeStart).to.equal(Lexer.LineMode.Default, "line 1 should start in Default mode"); - expect(Assert.asDefined(updated.lines[2]).lineModeStart).to.equal(Lexer.LineMode.Default, "line 2 should start in Default mode"); + expect(ArrayUtils.assertGet(updated.lines, 0).lineModeEnd).to.equal(Lexer.LineMode.Default, "line 0 should end in Default mode"); + expect(ArrayUtils.assertGet(updated.lines, 1).lineModeStart).to.equal(Lexer.LineMode.Default, "line 1 should start in Default mode"); + expect(ArrayUtils.assertGet(updated.lines, 2).lineModeStart).to.equal(Lexer.LineMode.Default, "line 2 should start in Default mode"); // Now snapshot to get token positions and verify line numbers are correct. const triedSnapshot: Lexer.TriedLexerSnapshot = Lexer.trySnapshot(updated); diff --git a/src/test/libraryTest/parser/identifierContextKind.test.ts b/src/test/libraryTest/parser/identifierContextKind.test.ts index dc89a169..597380d5 100644 --- a/src/test/libraryTest/parser/identifierContextKind.test.ts +++ b/src/test/libraryTest/parser/identifierContextKind.test.ts @@ -4,7 +4,7 @@ import "mocha"; import { expect } from "chai"; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; import { NodeIdMap, NodeIdMapUtils, ParseOk } from "../../../powerquery-parser/parser"; import { AssertTestUtils } from "../../testUtils"; import { Ast } from "../../../powerquery-parser/language"; @@ -29,7 +29,7 @@ function assertGetIdentifierByLiteral(parseOk: ParseOk, identifierLiteral: strin if (matches.length === 0) { throw new Error(`could not find the following identifier in the ast: ${identifierLiteral}`); } else if (matches.length === 1) { - return Assert.asDefined(matches[0]); + return ArrayUtils.assertGet(matches, 0); } else { throw new Error(`found multiple instances of the following identifier: ${identifierLiteral}`); } diff --git a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts index ce3429df..7254234e 100644 --- a/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts +++ b/src/test/libraryTest/parser/parseNodeIdMapUtils.test.ts @@ -4,7 +4,7 @@ import "mocha"; import { expect } from "chai"; -import { Assert, Language, MapUtils, Parser } from "../../../powerquery-parser"; +import { ArrayUtils, Assert, Language, MapUtils, Parser } from "../../../powerquery-parser"; import { DefaultSettings, Task } from "../../.."; import { FieldSpecificationKeyValuePair, @@ -34,7 +34,7 @@ describe("nodeIdMapIterator", () => { expect(fieldSpecificationListIds.size).to.equal(1); - const fieldSpecificationListId: number = Assert.asDefined([...fieldSpecificationListIds.values()][0]); + const fieldSpecificationListId: number = ArrayUtils.assertGet([...fieldSpecificationListIds.values()], 0); const fieldSpecificationList: TXorNode = NodeIdMapUtils.assertXor( parseOk.nodeIdMapCollection, @@ -46,11 +46,11 @@ describe("nodeIdMapIterator", () => { expect(fieldSpecificationKeyValuePairs.length).to.equal(2); - const firstKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[0]); + const firstKeyValuePair: FieldSpecificationKeyValuePair = ArrayUtils.assertGet(fieldSpecificationKeyValuePairs, 0); expect(firstKeyValuePair.optional).to.equal(undefined); expect(firstKeyValuePair.normalizedKeyLiteral).to.equal("foo"); - const secondKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[1]); + const secondKeyValuePair: FieldSpecificationKeyValuePair = ArrayUtils.assertGet(fieldSpecificationKeyValuePairs, 1); expect(Boolean(secondKeyValuePair.optional)).to.equal(true); expect(secondKeyValuePair.normalizedKeyLiteral).to.equal("bar"); }); @@ -67,7 +67,7 @@ describe("nodeIdMapIterator", () => { expect(functionExpressionIds.size).to.equal(1); - const functionExpressionId: number = Assert.asDefined([...functionExpressionIds.values()][0]); + const functionExpressionId: number = ArrayUtils.assertGet([...functionExpressionIds.values()], 0); const functionExpression: TXorNode = NodeIdMapUtils.assertXor( parseOk.nodeIdMapCollection, @@ -82,12 +82,12 @@ describe("nodeIdMapIterator", () => { expect(parameters.length).to.equal(2); const firstParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(Assert.asDefined(parameters[0])), + XorNodeUtils.assertAst(ArrayUtils.assertGet(parameters, 0)), Ast.NodeKind.Parameter, ); const secondParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(Assert.asDefined(parameters[1])), + XorNodeUtils.assertAst(ArrayUtils.assertGet(parameters, 1)), Ast.NodeKind.Parameter, ); @@ -106,7 +106,7 @@ describe("nodeIdMapIterator", () => { expect(functionExpressionIds.size).to.equal(1); - const functionExpressionId: number = Assert.asDefined([...functionExpressionIds.values()][0]); + const functionExpressionId: number = ArrayUtils.assertGet([...functionExpressionIds.values()], 0); const functionExpression: TXorNode = NodeIdMapUtils.assertXor( parseError.state.contextState.nodeIdMapCollection, @@ -121,12 +121,12 @@ describe("nodeIdMapIterator", () => { expect(parameters.length).to.equal(2); const firstParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(Assert.asDefined(parameters[0])), + XorNodeUtils.assertAst(ArrayUtils.assertGet(parameters, 0)), Ast.NodeKind.Parameter, ); const secondParameter: Ast.TParameter = Language.AstUtils.assertAsNodeKind( - XorNodeUtils.assertAst(Assert.asDefined(parameters[1])), + XorNodeUtils.assertAst(ArrayUtils.assertGet(parameters, 1)), Ast.NodeKind.Parameter, ); @@ -147,7 +147,7 @@ describe("nodeIdMapIterator", () => { expect(functionExpressionIds.size).to.equal(1); - const functionExpressionId: number = Assert.asDefined([...functionExpressionIds.values()][0]); + const functionExpressionId: number = ArrayUtils.assertGet([...functionExpressionIds.values()], 0); const functionExpression: TXorNode = NodeIdMapUtils.assertXor( parseOk.nodeIdMapCollection, @@ -173,7 +173,7 @@ describe("nodeIdMapIterator", () => { expect(functionExpressionIds.size).to.equal(1); - const functionExpressionId: number = Assert.asDefined([...functionExpressionIds.values()][0]); + const functionExpressionId: number = ArrayUtils.assertGet([...functionExpressionIds.values()], 0); const functionExpression: TXorNode = NodeIdMapUtils.assertXor( parseError.state.contextState.nodeIdMapCollection, @@ -201,7 +201,7 @@ describe("nodeIdMapIterator", () => { expect(recordIds.size).to.equal(1); - const recordId: number = Assert.asDefined([...recordIds.values()][0]); + const recordId: number = ArrayUtils.assertGet([...recordIds.values()], 0); const record: TXorNode = NodeIdMapUtils.assertXor(parseOk.nodeIdMapCollection, recordId); const recordKeyValuePairs: ReadonlyArray = NodeIdMapIterator.iterRecord( @@ -211,7 +211,7 @@ describe("nodeIdMapIterator", () => { expect(recordKeyValuePairs.length).to.equal(1); - const keyValuePair: RecordKeyValuePair = Assert.asDefined(recordKeyValuePairs[0]); + const keyValuePair: RecordKeyValuePair = ArrayUtils.assertGet(recordKeyValuePairs, 0); expect(keyValuePair.normalizedKeyLiteral).to.equal("foo"); }); }); @@ -227,7 +227,7 @@ describe("nodeIdMapIterator", () => { expect(recordTypeIds.size).to.equal(1); - const recordTypeId: number = Assert.asDefined([...recordTypeIds.values()][0]); + const recordTypeId: number = ArrayUtils.assertGet([...recordTypeIds.values()], 0); const recordType: TXorNode = NodeIdMapUtils.assertXor(parseOk.nodeIdMapCollection, recordTypeId); const fieldSpecificationKeyValuePairs: ReadonlyArray = @@ -235,11 +235,11 @@ describe("nodeIdMapIterator", () => { expect(fieldSpecificationKeyValuePairs.length).to.equal(2); - const firstKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[0]); + const firstKeyValuePair: FieldSpecificationKeyValuePair = ArrayUtils.assertGet(fieldSpecificationKeyValuePairs, 0); expect(firstKeyValuePair.optional).to.equal(undefined); expect(firstKeyValuePair.normalizedKeyLiteral).to.equal("foo"); - const secondKeyValuePair: FieldSpecificationKeyValuePair = Assert.asDefined(fieldSpecificationKeyValuePairs[1]); + const secondKeyValuePair: FieldSpecificationKeyValuePair = ArrayUtils.assertGet(fieldSpecificationKeyValuePairs, 1); expect(Boolean(secondKeyValuePair.optional)).to.equal(true); expect(secondKeyValuePair.normalizedKeyLiteral).to.equal("bar"); }); diff --git a/src/test/libraryTest/parser/typeDirective.test.ts b/src/test/libraryTest/parser/typeDirective.test.ts index 9ddaf5d0..fef3c3a3 100644 --- a/src/test/libraryTest/parser/typeDirective.test.ts +++ b/src/test/libraryTest/parser/typeDirective.test.ts @@ -6,7 +6,7 @@ import { expect } from "chai"; import * as AssertTestUtils from "../../testUtils/assertTestUtils"; import { DefaultSettings, Language } from "../../../powerquery-parser"; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; type ParseOk = Awaited>; @@ -22,7 +22,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; + const variable: Language.Ast.IdentifierPairedExpression = ArrayUtils.assertGet(letExpression.variableList.elements, 0).node; expect(variable.precedingDirectives).to.equal(undefined); }); @@ -41,7 +41,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; + const variable: Language.Ast.IdentifierPairedExpression = ArrayUtils.assertGet(letExpression.variableList.elements, 0).node; expect(variable.precedingDirectives).to.not.equal(undefined); @@ -62,7 +62,7 @@ shared Value = [];`, ); const section: Language.Ast.Section = parseOk.ast as Language.Ast.Section; - const sectionMember: Language.Ast.SectionMember = Assert.asDefined(section.sectionMembers.elements[0]); + const sectionMember: Language.Ast.SectionMember = ArrayUtils.assertGet(section.sectionMembers.elements, 0); expect( sectionMember.precedingDirectives?.map((directive: Language.Comment.TDirective) => directive.value), @@ -83,7 +83,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; + const variable: Language.Ast.IdentifierPairedExpression = ArrayUtils.assertGet(letExpression.variableList.elements, 0).node; expect( variable.precedingDirectives?.map((directive: Language.Comment.TDirective) => directive.value), @@ -105,7 +105,7 @@ in ); const letExpression: Language.Ast.LetExpression = parseOk.ast as Language.Ast.LetExpression; - const variable: Language.Ast.IdentifierPairedExpression = Assert.asDefined(letExpression.variableList.elements[0]).node; + const variable: Language.Ast.IdentifierPairedExpression = ArrayUtils.assertGet(letExpression.variableList.elements, 0).node; expect(variable.precedingDirectives).to.equal(undefined); }); diff --git a/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts b/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts index abe64eff..d7e6d946 100644 --- a/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts +++ b/src/test/libraryTest/tokenizer/tokenizerIncremental.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { DefaultSettings, Lexer, ResultUtils } from "../../.."; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; import { ILineTokens, IState, IToken, Tokenizer } from "../../testUtils/tokenizerTestUtils"; const tokenizer: Tokenizer = new Tokenizer("\n"); @@ -75,11 +75,11 @@ class MockDocument { if (startingIndex === 0 || this.lineEndStates[startingIndex - 1] === undefined) { state = this.tokenizer.getInitialState(); } else { - state = Assert.asDefined(this.lineEndStates[startingIndex - 1]); + state = ArrayUtils.assertGet(this.lineEndStates, startingIndex - 1); } for (let index: number = startingIndex; index < this.lines.length; index += 1) { - const result: ILineTokens = tokenizer.tokenize(Assert.asDefined(this.lines[index]), state); + const result: ILineTokens = tokenizer.tokenize(ArrayUtils.assertGet(this.lines, index), state); this.lineTokens[index] = result.tokens; tokenizedLineCount += 1; @@ -161,14 +161,14 @@ describe("MockDocument validation", () => { describe("Incremental updates", () => { it("Re-parse with no change", () => { const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const originalLine: string = Assert.asDefined(document.lines[2]); + const originalLine: string = ArrayUtils.assertGet(document.lines, 2); const count: number = document.applyChangeAndTokenize(originalLine, 2); expect(count).equals(1, "we should not have tokenized more than one line"); }); it("Re-parse with simple change", () => { const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = Assert.asDefined(document.lines[2]).replace("source", "source123"); + const modified: string = ArrayUtils.assertGet(document.lines, 2).replace("source", "source123"); const count: number = document.applyChangeAndTokenize(modified, 2); expect(count).equals(1, "we should not have tokenized more than one line"); }); @@ -176,12 +176,12 @@ describe("Incremental updates", () => { it("Re-parse with unterminated string", () => { const lineNumber: number = 4; const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = Assert.asDefined(document.lines[lineNumber]).replace(`"text",`, `"text`); + const modified: string = ArrayUtils.assertGet(document.lines, lineNumber).replace(`"text",`, `"text`); const count: number = document.applyChangeAndTokenize(modified, lineNumber); expect(count).equals(document.lines.length - lineNumber, "remaining lines should have been tokenized"); for (let index: number = lineNumber + 1; index < document.lineTokens.length; index += 1) { - const lineTokens: ReadonlyArray = Assert.asDefined(document.lineTokens[index]); + const lineTokens: ReadonlyArray = ArrayUtils.assertGet(document.lineTokens, index); lineTokens.forEach((token: IToken) => { expect(token.scopes).equals("TextContent", "expecting remaining tokens to be strings"); @@ -192,12 +192,12 @@ describe("Incremental updates", () => { it("Re-parse with unterminated block comment", () => { const lineNumber: number = 3; const document: MockDocument = new MockDocument(ORIGINAL_QUERY); - const modified: string = Assert.asDefined(document.lines[lineNumber]).replace(`rce),`, `rce), /* my open comment`); + const modified: string = ArrayUtils.assertGet(document.lines, lineNumber).replace(`rce),`, `rce), /* my open comment`); const count: number = document.applyChangeAndTokenize(modified, lineNumber); expect(count).equals(document.lines.length - lineNumber, "remaining lines should have been tokenized"); for (let index: number = lineNumber + 1; index < document.lineTokens.length; index += 1) { - const lineTokens: ReadonlyArray = Assert.asDefined(document.lineTokens[index]); + const lineTokens: ReadonlyArray = ArrayUtils.assertGet(document.lineTokens, index); lineTokens.forEach((token: IToken) => { expect(token.scopes).equals("MultilineCommentContent", "expecting remaining tokens to be comments"); diff --git a/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts b/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts index f278db6d..34b7e2c2 100644 --- a/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts +++ b/src/test/libraryTest/tokenizer/tokenizerSimple.test.ts @@ -5,7 +5,7 @@ import "mocha"; import { expect } from "chai"; import { ILineTokens, IState, IToken, Tokenizer, TokenizerState } from "../../testUtils/tokenizerTestUtils"; -import { Assert } from "../../../powerquery-parser/common"; +import { ArrayUtils } from "../../../powerquery-parser/common"; const tokenizer: Tokenizer = new Tokenizer(`\n`); const initialState: TokenizerState = tokenizer.getInitialState() as TokenizerState; @@ -17,14 +17,14 @@ function tokenizeLines(query: string, expectedTokenCounts: number[]): void { expect(lines.length).equals(expectedTokenCounts.length); for (let index: number = 0; index < lines.length; index += 1) { - const r: ILineTokens = tokenizer.tokenize(Assert.asDefined(lines[index]), state); + const r: ILineTokens = tokenizer.tokenize(ArrayUtils.assertGet(lines, index), state); expect(!state.equals(r.endState), `state should have changed.`); - expect(r.tokens.length).equals(Assert.asDefined(expectedTokenCounts[index]), `unexpected token count`); + expect(r.tokens.length).equals(ArrayUtils.assertGet(expectedTokenCounts, index), `unexpected token count`); state = r.endState as TokenizerState; r.tokens.forEach((token: IToken) => { - expect(token.startIndex).is.lessThan(Assert.asDefined(lines[index]).length); + expect(token.startIndex).is.lessThan(ArrayUtils.assertGet(lines, index).length); }); } } diff --git a/src/test/testUtils/tokenizerTestUtils.ts b/src/test/testUtils/tokenizerTestUtils.ts index cb079395..4d133520 100644 --- a/src/test/testUtils/tokenizerTestUtils.ts +++ b/src/test/testUtils/tokenizerTestUtils.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert } from "../../powerquery-parser/common"; +import { ArrayUtils } from "../../powerquery-parser/common"; import { DefaultLocale, Language, ResultUtils } from "../../powerquery-parser"; import { Lexer } from "../.."; @@ -50,7 +50,7 @@ export class Tokenizer implements TokensProvider { const newLexerState: Lexer.State = triedLex.value; return { - tokens: Assert.asDefined(newLexerState.lines[newLexerState.lines.length - 1]).tokens.map(Tokenizer.ITokenFrom), + tokens: ArrayUtils.assertGet(newLexerState.lines, newLexerState.lines.length - 1).tokens.map(Tokenizer.ITokenFrom), endState: new TokenizerState(newLexerState), }; } @@ -83,8 +83,8 @@ export class TokenizerState implements IState { } // Compare last line state. - const leftLastLine: Lexer.TLine = Assert.asDefined(this.lexerState.lines[this.lexerState.lines.length - 1]); - const rightLastLine: Lexer.TLine = Assert.asDefined(rightLexerState.lines[rightLexerState.lines.length - 1]); + const leftLastLine: Lexer.TLine = ArrayUtils.assertGet(this.lexerState.lines, this.lexerState.lines.length - 1); + const rightLastLine: Lexer.TLine = ArrayUtils.assertGet(rightLexerState.lines, rightLexerState.lines.length - 1); return leftLastLine.lineModeEnd === rightLastLine.lineModeEnd; } From 7325e17c497fb45794b763006138711a286ebf4d Mon Sep 17 00:00:00 2001 From: "Jordan Bolton (jobolton)" Date: Mon, 8 Jun 2026 16:51:27 -0500 Subject: [PATCH 3/3] Fix DotDot lexing: don't assertGet past end of line When lexing '..' at end of line, chr3 at positionStart+2 may not exist. Use optional indexing instead of StringUtils.assertGet since the character may legitimately be absent (DotDot vs Ellipsis lookahead). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/powerquery-parser/lexer/lexer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/powerquery-parser/lexer/lexer.ts b/src/powerquery-parser/lexer/lexer.ts index 74507daf..e9c557ca 100644 --- a/src/powerquery-parser/lexer/lexer.ts +++ b/src/powerquery-parser/lexer/lexer.ts @@ -855,7 +855,7 @@ function tokenizeDefault(line: TLine, lineNumber: number, positionStart: number, } else if ("1" <= chr2 && chr2 <= "9") { token = readNumericLiteral(text, lineNumber, positionStart, locale); } else if (chr2 === ".") { - const chr3: string = StringUtils.assertGet(text, positionStart + 2); + const chr3: string | undefined = text[positionStart + 2]; if (chr3 === ".") { token = readConstant(Token.LineTokenKind.Ellipsis, text, positionStart, 3);