diff --git a/.eslintrc.js b/.eslintrc.js index b4aeafa9..346579db 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,8 +13,8 @@ module.exports = { "@typescript-eslint/await-thenable": "error", "@typescript-eslint/consistent-type-assertions": ["warn", { assertionStyle: "as" }], "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], "@typescript-eslint/prefer-namespace-keyword": "error", diff --git a/package-lock.json b/package-lock.json index 689986ba..0c5b59c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.6.1", + "version": "0.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@microsoft/powerquery-parser", - "version": "0.6.1", + "version": "0.6.2", "license": "MIT", "dependencies": { "grapheme-splitter": "^1.0.4", diff --git a/package.json b/package.json index a37484c8..6ebd7e22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/powerquery-parser", - "version": "0.6.1", + "version": "0.6.2", "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 bb171938..d53b7d42 100644 --- a/src/powerquery-parser/common/arrayUtils.ts +++ b/src/powerquery-parser/common/arrayUtils.ts @@ -63,7 +63,7 @@ export function assertNonZeroLength( ); } -export async function asyncMap( +export async function mapAsync( collection: ReadonlyArray, mapFn: (value: T) => Promise, ): Promise> { diff --git a/src/powerquery-parser/common/result/resultUtils.ts b/src/powerquery-parser/common/result/resultUtils.ts index 90752383..b45e0b90 100644 --- a/src/powerquery-parser/common/result/resultUtils.ts +++ b/src/powerquery-parser/common/result/resultUtils.ts @@ -36,7 +36,7 @@ export function ensureResult(locale: string, callbackFn: () => T): Result( +export async function ensureResultAsync( locale: string, callbackFn: () => Promise, ): Promise> { diff --git a/src/powerquery-parser/common/traversal.ts b/src/powerquery-parser/common/traversal.ts index 1920ae14..3d12e5c8 100644 --- a/src/powerquery-parser/common/traversal.ts +++ b/src/powerquery-parser/common/traversal.ts @@ -1,23 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import { Assert, CommonError, Result } from "."; +import { ArrayUtils, Assert, CommonError, Result } from "."; import { NodeIdMap, NodeIdMapUtils, ParseContext, TXorNode, XorNodeKind, XorNodeUtils } from "../parser"; import { Ast } from "../language"; import { ResultUtils } from "./result"; +import { Settings } from ".."; +import { Trace } from "./trace"; export type TriedTraverse = Result; export type TVisitNodeFn, ResultType, Node, Return> = ( state: State, node: Node, -) => Return; - -export type TVisitChildNodeFn, ResultType, Node, Return> = ( - state: State, - parent: Node, - node: Node, -) => Return; +) => Promise; export type TEarlyExitFn, ResultType, Node> = TVisitNodeFn< State, @@ -30,15 +26,14 @@ export type TExpandNodesFn, ResultType state: State, node: Node, collection: NodesById, -) => ReadonlyArray; +) => Promise>; export const enum VisitNodeStrategy { BreadthFirst = "BreadthFirst", DepthFirst = "DepthFirst", } -export interface ITraversalState { - readonly locale: string; +export interface ITraversalState extends Pick { result: T; } @@ -51,7 +46,7 @@ export function tryTraverseAst, Result visitNodeFn: TVisitNodeFn, expandNodesFn: TExpandNodesFn, maybeEarlyExitFn: TEarlyExitFn | undefined, -): TriedTraverse { +): Promise> { return tryTraverse( state, nodeIdMapCollection, @@ -72,7 +67,7 @@ export function tryTraverseXor, Result visitNodeFn: TVisitNodeFn, expandNodesFn: TExpandNodesFn, maybeEarlyExitFn: TEarlyExitFn | undefined, -): TriedTraverse { +): Promise> { return tryTraverse( state, nodeIdMapCollection, @@ -92,9 +87,9 @@ export function tryTraverse, ResultTyp visitNodeFn: TVisitNodeFn, expandNodesFn: TExpandNodesFn, maybeEarlyExitFn: TEarlyExitFn | undefined, -): TriedTraverse { - return ResultUtils.ensureResult(state.locale, () => { - traverseRecursion( +): Promise> { + return ResultUtils.ensureResultAsync(state.locale, async () => { + await traverseRecursion( state, nodesById, root, @@ -109,11 +104,12 @@ export function tryTraverse, ResultTyp } // a TExpandNodesFn usable by tryTraverseAst which visits all nodes. -export function assertGetAllAstChildren, ResultType>( +// eslint-disable-next-line require-await +export async function assertGetAllAstChildren, ResultType>( _state: State, astNode: Ast.TNode, nodeIdMapCollection: NodeIdMap.Collection, -): ReadonlyArray { +): Promise> { const maybeChildIds: ReadonlyArray | undefined = nodeIdMapCollection.childIdsById.get(astNode.id); if (maybeChildIds) { @@ -126,16 +122,23 @@ export function assertGetAllAstChildren, ResultType>( +// eslint-disable-next-line require-await +export async function assertGetAllXorChildren, ResultType>( _state: State, xorNode: TXorNode, nodeIdMapCollection: NodeIdMap.Collection, -): ReadonlyArray { +): Promise> { switch (xorNode.kind) { case XorNodeKind.Ast: { const astNode: Ast.TNode = xorNode.node; - return assertGetAllAstChildren(_state, astNode, nodeIdMapCollection).map(XorNodeUtils.boxAst); + const children: ReadonlyArray = await assertGetAllAstChildren( + _state, + astNode, + nodeIdMapCollection, + ); + + return ArrayUtils.mapAsync(children, (value: Ast.TNode) => Promise.resolve(XorNodeUtils.boxAst(value))); } case XorNodeKind.Context: { @@ -167,13 +170,13 @@ export function maybeExpandXorParent( _state: T, xorNode: TXorNode, nodeIdMapCollection: NodeIdMap.Collection, -): ReadonlyArray { +): Promise> { const maybeParent: TXorNode | undefined = NodeIdMapUtils.maybeParentXor(nodeIdMapCollection, xorNode.node.id); - return maybeParent !== undefined ? [maybeParent] : []; + return Promise.resolve(maybeParent !== undefined ? [maybeParent] : []); } -function traverseRecursion, ResultType, Node, NodesById>( +async function traverseRecursion, ResultType, Node, NodesById>( state: State, nodesById: NodesById, node: Node, @@ -181,18 +184,24 @@ function traverseRecursion, ResultType visitNodeFn: TVisitNodeFn, expandNodesFn: TExpandNodesFn, maybeEarlyExitFn: TEarlyExitFn | undefined, -): void { - if (maybeEarlyExitFn && maybeEarlyExitFn(state, node)) { +): Promise { + const trace: Trace = state.traceManager.entry("Traversal", traverseRecursion.name); + state.maybeCancellationToken?.throwIfCancelled(); + + if (maybeEarlyExitFn && (await maybeEarlyExitFn(state, node))) { return; } else if (strategy === VisitNodeStrategy.BreadthFirst) { - visitNodeFn(state, node); + await visitNodeFn(state, node); } - for (const child of expandNodesFn(state, node, nodesById)) { - traverseRecursion(state, nodesById, child, strategy, visitNodeFn, expandNodesFn, maybeEarlyExitFn); + for (const child of await expandNodesFn(state, node, nodesById)) { + // eslint-disable-next-line no-await-in-loop + await traverseRecursion(state, nodesById, child, strategy, visitNodeFn, expandNodesFn, maybeEarlyExitFn); } if (strategy === VisitNodeStrategy.DepthFirst) { - visitNodeFn(state, node); + await visitNodeFn(state, node); } + + trace.exit(); } diff --git a/src/test/libraryTest/parser/children.ts b/src/test/libraryTest/parser/children.ts index 3800cf06..d4150a30 100644 --- a/src/test/libraryTest/parser/children.ts +++ b/src/test/libraryTest/parser/children.ts @@ -29,7 +29,7 @@ function createActual(lexParseOk: Task.ParseTaskOk): ChildIdsByIdEntry[] { } describe("Parser.Children", () => { - it(`WIP () as number => 1`, async () => { + it(`() as number => 1`, async () => { const text: string = `() as number => 1`; const expected: ReadonlyArray = [ diff --git a/src/test/libraryTest/parser/idUtils.ts b/src/test/libraryTest/parser/idUtils.ts index 9aa3fb28..c28941bb 100644 --- a/src/test/libraryTest/parser/idUtils.ts +++ b/src/test/libraryTest/parser/idUtils.ts @@ -8,6 +8,7 @@ import { Assert, Language, TaskUtils, Traverse } from "../../../powerquery-parse import { ChildIdsById, IdsByNodeKind, ParentIdById } from "../../../powerquery-parser/parser/nodeIdMap/nodeIdMap"; import { DefaultSettings, Task } from "../../.."; import { NodeIdMap, TXorNode, XorNodeUtils } from "../../../powerquery-parser/parser"; +import { NoOpTraceManager } from "../../../powerquery-parser/common/trace"; type TraverseState = Traverse.ITraversalState & Pick & { astIds: number[]; contextIds: number[] }; @@ -48,7 +49,10 @@ function createSimplifiedParentIdById(parentIdById: ParentIdById): ReadonlyArray return [...parentIdById.entries()].sort(); } -function expectLinksMatch(triedLexParse: Task.TriedLexParseTask, expected: AbridgedNodeIdMapCollection): void { +async function expectLinksMatch( + triedLexParse: Task.TriedLexParseTask, + expected: AbridgedNodeIdMapCollection, +): Promise { let nodeIdMapCollection: NodeIdMap.Collection; let xorNode: TXorNode; @@ -78,9 +82,11 @@ function expectLinksMatch(triedLexParse: Task.TriedLexParseTask, expected: Abrid contextIds: [], leafIds: new Set(), idsByNodeKind: new Map(), + maybeCancellationToken: undefined, + traceManager: new NoOpTraceManager(), }; - const triedTraverse: Traverse.TriedTraverse = Traverse.tryTraverseXor( + const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseXor( traverseState, triedLexParse.nodeIdMapCollection, xorNode, @@ -185,7 +191,8 @@ function assertTraverseMatchesState(traverseState: TraverseState, nodeIdMapColle ); } -function traverseVisitNode(state: TraverseState, xorNode: TXorNode): void { +// eslint-disable-next-line require-await +async function traverseVisitNode(state: TraverseState, xorNode: TXorNode): Promise { if (XorNodeUtils.isAstXor(xorNode)) { state.astIds.push(xorNode.node.id); @@ -219,7 +226,7 @@ describe("idUtils", () => { }; const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text); - expectLinksMatch(triedLexParse, expected); + await expectLinksMatch(triedLexParse, expected); }); it(`-1`, async () => { @@ -247,7 +254,7 @@ describe("idUtils", () => { }; const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text); - expectLinksMatch(triedLexParse, expected); + await expectLinksMatch(triedLexParse, expected); }); it(`1 + 2`, async () => { @@ -271,7 +278,7 @@ describe("idUtils", () => { }; const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text); - expectLinksMatch(triedLexParse, expected); + await expectLinksMatch(triedLexParse, expected); }); it(`foo()`, async () => { @@ -307,6 +314,6 @@ describe("idUtils", () => { }; const triedLexParse: Task.TriedLexParseTask = await TaskUtils.tryLexParse(DefaultSettings, text); - expectLinksMatch(triedLexParse, expected); + await expectLinksMatch(triedLexParse, expected); }); }); diff --git a/src/test/libraryTest/parser/simple.ts b/src/test/libraryTest/parser/simple.ts index 0d47c2ad..2518c0d1 100644 --- a/src/test/libraryTest/parser/simple.ts +++ b/src/test/libraryTest/parser/simple.ts @@ -15,6 +15,7 @@ import { TaskUtils, Traverse, } from "../../.."; +import { NoOpTraceManager } from "../../../powerquery-parser/common/trace"; import { TestAssertUtils } from "../../testUtils"; type AbridgedNode = [Language.Ast.NodeKind, number | undefined]; @@ -33,9 +34,11 @@ async function collectAbridgeNodeFromAst(text: string): Promise = Traverse.tryTraverseAst< + const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseAst< CollectAbridgeNodeState, AbridgedNode[] >( @@ -66,9 +69,11 @@ async function assertGetNthNodeOfKind( nodeKind, nthCounter: 0, nthRequired, + maybeCancellationToken: undefined, + traceManager: new NoOpTraceManager(), }; - const triedTraverse: Traverse.TriedTraverse = Traverse.tryTraverseAst< + const triedTraverse: Traverse.TriedTraverse = await Traverse.tryTraverseAst< NthNodeOfKindState, Language.Ast.TNode | undefined >( @@ -86,11 +91,13 @@ async function assertGetNthNodeOfKind( return Assert.asDefined(triedTraverse.value) as N; } -function collectAbridgeNodeVisit(state: CollectAbridgeNodeState, node: Language.Ast.TNode): void { +// eslint-disable-next-line require-await +async function collectAbridgeNodeVisit(state: CollectAbridgeNodeState, node: Language.Ast.TNode): Promise { state.result.push([node.kind, node.maybeAttributeIndex]); } -function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): void { +// eslint-disable-next-line require-await +async function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): Promise { if (node.kind === state.nodeKind) { state.nthCounter += 1; @@ -100,7 +107,8 @@ function nthNodeVisit(state: NthNodeOfKindState, node: Language.Ast.TNode): void } } -function nthNodeEarlyExit(state: NthNodeOfKindState, _: Language.Ast.TNode): boolean { +// eslint-disable-next-line require-await +async function nthNodeEarlyExit(state: NthNodeOfKindState, _: Language.Ast.TNode): Promise { return state.nthCounter === state.nthRequired; }