Skip to content

Commit

Permalink
fix: add missing members to parser exception type declarations (#1322)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbett7 authored Jan 16, 2021
1 parent f6f2b12 commit 9045686
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 90 deletions.
37 changes: 31 additions & 6 deletions packages/chevrotain/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2280,37 +2280,62 @@ export interface IRecognitionException {
*/
export declare function isRecognitionException(error: Error): boolean

// TODO refactor exceptions constructors to classes

/**
* An exception of this type will be saved in {@link Parser.errors} when {@link Parser.CONSUME}
* was called but failed to match the expected Token Type.
*/
export declare class MismatchedTokenException extends Error {
export declare class MismatchedTokenException
extends Error
implements IRecognitionException {
context: IRecognizerContext
resyncedTokens: IToken[]
token: IToken
previousToken: IToken

constructor(message: string, token: IToken, previousToken: IToken)
}

/**
* An exception of this type will be saved in {@link Parser.errors} when {@link Parser.OR}
* was called yet none of the possible alternatives could be matched.
*/
export declare class NoViableAltException extends Error {
export declare class NoViableAltException
extends Error
implements IRecognitionException {
context: IRecognizerContext
resyncedTokens: IToken[]
token: IToken
previousToken: IToken

constructor(message: string, token: IToken, previousToken: IToken)
}

/**
* An exception of this type will be saved in {@link Parser.errors} when
* the parser has finished yet there exists remaining input (tokens) that has not processed.
*/
export declare class NotAllInputParsedException extends Error {
export declare class NotAllInputParsedException
extends Error
implements IRecognitionException {
context: IRecognizerContext
resyncedTokens: IToken[]
token: IToken

constructor(message: string, token: IToken)
}

/**
* An exception of this type will be saved in {@link Parser.errors} when {@link Parser.AT_LEAST_ONE}
* or {@link Parser.AT_LEAST_ONE_SEP} was called but failed to match even a single iteration.
*/
export declare class EarlyExitException extends Error {
export declare class EarlyExitException
extends Error
implements IRecognitionException {
context: IRecognizerContext
resyncedTokens: IToken[]
token: IToken
previousToken: IToken

constructor(message: string, token: IToken, previousToken: IToken)
}

Expand Down
1 change: 1 addition & 0 deletions packages/chevrotain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"@types/mocha": "^8.0.3",
"@types/sinon-chai": "^3.2.0",
"chai": "^4.1.2",
"error-stack-parser": "^2.0.6",
"esm": "^3.2.25",
"gitty": "^3.6.0",
"jsdom": "16.4.0",
Expand Down
31 changes: 17 additions & 14 deletions packages/chevrotain/scripts/fix-coverage-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@
const fs = require("fs-extra")
const path = require("path")

const interPath = path.resolve(
__dirname,
"../lib/src/parse/grammar/interpreter.js"
)
// call to ignore errors introduced by TS downleveling class inheritance to ES5
function fixClassConstructorSuperCalls(filePath) {
const interPath = path.resolve(__dirname, filePath)

const interString = fs.readFileSync(interPath, "utf8").toString()
let fixedInterString = interString.replace(
"var __extends =",
"/* istanbul ignore next */ var __extends ="
)
const interString = fs.readFileSync(interPath, "utf8").toString()
let fixedInterString = interString.replace(
"var __extends =",
"/* istanbul ignore next */ var __extends ="
)

fixedInterString = fixedInterString.replace(
/\|\| this/g,
"/* istanbul ignore next */ || this"
)
fixedInterString = fixedInterString.replace(
/\|\| this/g,
"/* istanbul ignore next */ || this"
)

fs.writeFileSync(interPath, fixedInterString)
fs.writeFileSync(interPath, fixedInterString)
}

fixClassConstructorSuperCalls("../lib/src/parse/exceptions_public.js")
fixClassConstructorSuperCalls("../lib/src/parse/grammar/interpreter.js")
82 changes: 38 additions & 44 deletions packages/chevrotain/src/parse/exceptions_public.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contains } from "../utils/utils"
import { IToken } from "../../api"
import { IToken, IRecognitionException, IRecognizerContext } from "../../api"

const MISMATCHED_TOKEN_EXCEPTION = "MismatchedTokenException"
const NO_VIABLE_ALT_EXCEPTION = "NoViableAltException"
Expand All @@ -21,55 +21,49 @@ export function isRecognitionException(error: Error) {
return contains(RECOGNITION_EXCEPTION_NAMES, error.name)
}

export function MismatchedTokenException(
message: string,
token: IToken,
previousToken: IToken
) {
this.name = MISMATCHED_TOKEN_EXCEPTION
this.message = message
this.token = token
this.previousToken = previousToken
this.resyncedTokens = []
}
abstract class RecognitionException
extends Error
implements IRecognitionException {
context: IRecognizerContext
resyncedTokens = []

// must use the "Error.prototype" instead of "new Error"
// because the stack trace points to where "new Error" was invoked"
MismatchedTokenException.prototype = Error.prototype
protected constructor(message: string, public token: IToken) {
super(message)

export function NoViableAltException(
message: string,
token: IToken,
previousToken: IToken
) {
this.name = NO_VIABLE_ALT_EXCEPTION
this.message = message
this.token = token
this.previousToken = previousToken
this.resyncedTokens = []
}
// fix prototype chain when typescript target is ES5
Object.setPrototypeOf(this, new.target.prototype)

NoViableAltException.prototype = Error.prototype
/* istanbul ignore next - V8 workaround to remove constructor from stacktrace when typescript target is ES5 */
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
}

export function NotAllInputParsedException(message: string, token: IToken) {
this.name = NOT_ALL_INPUT_PARSED_EXCEPTION
this.message = message
this.token = token
this.resyncedTokens = []
export class MismatchedTokenException extends RecognitionException {
constructor(message: string, token: IToken, public previousToken: IToken) {
super(message, token)
this.name = MISMATCHED_TOKEN_EXCEPTION
}
}

NotAllInputParsedException.prototype = Error.prototype
export class NoViableAltException extends RecognitionException {
constructor(message: string, token: IToken, public previousToken: IToken) {
super(message, token)
this.name = NO_VIABLE_ALT_EXCEPTION
}
}

export function EarlyExitException(
message: string,
token: IToken,
previousToken: IToken
) {
this.name = EARLY_EXIT_EXCEPTION
this.message = message
this.token = token
this.previousToken = previousToken
this.resyncedTokens = []
export class NotAllInputParsedException extends RecognitionException {
constructor(message: string, token: IToken) {
super(message, token)
this.name = NOT_ALL_INPUT_PARSED_EXCEPTION
}
}

EarlyExitException.prototype = Error.prototype
export class EarlyExitException extends RecognitionException {
constructor(message: string, token: IToken, public previousToken: IToken) {
super(message, token)
this.name = EARLY_EXIT_EXCEPTION
}
}
119 changes: 97 additions & 22 deletions packages/chevrotain/test/parse/exceptions_spec.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,120 @@
import * as ErrorStackParser from "error-stack-parser"
import { createTokenInstance, EOF } from "../../src/scan/tokens_public"
import {
EarlyExitException,
NoViableAltException,
NotAllInputParsedException,
MismatchedTokenException
} from "../../src/parse/exceptions_public"
import { functionName } from "../../src/lang/lang_extensions"

describe("Chevrotain's Parsing Exceptions", () => {
describe("the mappings between a an exception instance and its matching an exception's name for: ", () => {
let isRunningInNodeJS = module && module.exports
let it_node = isRunningInNodeJS ? it : it.skip
describe("the exception instance subclasses Error with the right properties for: ", () => {
let currentToken = createTokenInstance(EOF, "cur", -1, -1, -1, -1, -1, -1)
let previousToken = createTokenInstance(EOF, "prv", -1, -1, -1, -1, -1, -1)

let dummyToken = createTokenInstance(EOF, "", -1, -1, -1, -1, -1, -1)

it_node("EarlyExitException", () => {
let exceptionInstance = new EarlyExitException("", dummyToken, dummyToken)
expect(exceptionInstance.name).to.equal(functionName(exceptionInstance))
it("EarlyExitException", () => {
let exceptionInstance = new EarlyExitException(
"error message",
currentToken,
previousToken
)
expect(exceptionInstance).to.be.an.instanceOf(EarlyExitException)
expect(exceptionInstance).to.be.an.instanceOf(Error)
expect(exceptionInstance.name).to.equal("EarlyExitException")
expect(exceptionInstance.message).to.equal("error message")
expect(exceptionInstance.token).to.equal(currentToken)
expect(exceptionInstance.previousToken).to.equal(previousToken)
expect(exceptionInstance.resyncedTokens).to.be.empty
})

it_node("NoViableAltException", () => {
it("NoViableAltException", () => {
let exceptionInstance = new NoViableAltException(
"",
dummyToken,
dummyToken
"error message",
currentToken,
previousToken
)
expect(exceptionInstance.name).to.equal(functionName(exceptionInstance))
expect(exceptionInstance).to.be.an.instanceOf(NoViableAltException)
expect(exceptionInstance).to.be.an.instanceOf(Error)
expect(exceptionInstance.name).to.equal("NoViableAltException")
expect(exceptionInstance.message).to.equal("error message")
expect(exceptionInstance.token).to.equal(currentToken)
expect(exceptionInstance.previousToken).to.equal(previousToken)
expect(exceptionInstance.resyncedTokens).to.be.empty
})

it_node("NotAllInputParsedException", () => {
let exceptionInstance = new NotAllInputParsedException("", dummyToken)
expect(exceptionInstance.name).to.equal(functionName(exceptionInstance))
it("NotAllInputParsedException", () => {
let exceptionInstance = new NotAllInputParsedException(
"error message",
currentToken
)
expect(exceptionInstance).to.be.an.instanceOf(NotAllInputParsedException)
expect(exceptionInstance).to.be.an.instanceOf(Error)
expect(exceptionInstance.name).to.equal("NotAllInputParsedException")
expect(exceptionInstance.message).to.equal("error message")
expect(exceptionInstance.token).to.equal(currentToken)
expect(exceptionInstance.resyncedTokens).to.be.empty
})

it_node("MismatchedTokenException", () => {
it("MismatchedTokenException", () => {
let exceptionInstance = new MismatchedTokenException(
"",
dummyToken,
dummyToken
"error message",
currentToken,
previousToken
)
expect(exceptionInstance).to.be.an.instanceOf(MismatchedTokenException)
expect(exceptionInstance).to.be.an.instanceOf(Error)
expect(exceptionInstance.name).to.equal("MismatchedTokenException")
expect(exceptionInstance.message).to.equal("error message")
expect(exceptionInstance.token).to.equal(currentToken)
expect(exceptionInstance.resyncedTokens).to.be.empty
})
})

describe("the exception instance stacktrace is valid for: ", () => {
let dummyToken = createTokenInstance(EOF, "cur", -1, -1, -1, -1, -1, -1)

function throwAndCatchException(errorFactory: () => Error) {
try {
throw errorFactory()
} catch (e) {
return e
}
}

it("EarlyExitException", () => {
let exceptionInstance = throwAndCatchException(
() => new EarlyExitException("", dummyToken, dummyToken)
)
let stacktrace = ErrorStackParser.parse(exceptionInstance)
expect(stacktrace[0].functionName).to.be.undefined // lambda function
expect(stacktrace[1].functionName).to.equal("throwAndCatchException")
})

it("NoViableAltException", () => {
let exceptionInstance = throwAndCatchException(
() => new NoViableAltException("", dummyToken, dummyToken)
)
let stacktrace = ErrorStackParser.parse(exceptionInstance)
expect(stacktrace[0].functionName).to.be.undefined // lambda function
expect(stacktrace[1].functionName).to.equal("throwAndCatchException")
})

it("NotAllInputParsedException", () => {
let exceptionInstance = throwAndCatchException(
() => new NotAllInputParsedException("", dummyToken)
)
let stacktrace = ErrorStackParser.parse(exceptionInstance)
expect(stacktrace[0].functionName).to.be.undefined // lambda function
expect(stacktrace[1].functionName).to.equal("throwAndCatchException")
})

it("MismatchedTokenException", () => {
let exceptionInstance = throwAndCatchException(
() => new MismatchedTokenException("", dummyToken, dummyToken)
)
expect(exceptionInstance.name).to.equal(functionName(exceptionInstance))
let stacktrace = ErrorStackParser.parse(exceptionInstance)
expect(stacktrace[0].functionName).to.be.undefined // lambda function
expect(stacktrace[1].functionName).to.equal("throwAndCatchException")
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export type TypeCheckForApi = [
/** @see https://github.com/SAP/chevrotain/blob/87ed262c36b6f5cb4073e14f4f59901146c6c7a5/packages/chevrotain/src/api.ts#L213-L215*/
CstParser: any
EmbeddedActionsParser: any
MismatchedTokenException: ErrorConstructor
NoViableAltException: ErrorConstructor
NotAllInputParsedException: ErrorConstructor
EarlyExitException: ErrorConstructor
},
typeof apiDefs
>,
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5072,6 +5072,13 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"

error-stack-parser@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8"
integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==
dependencies:
stackframe "^1.1.1"

es-abstract@^1.17.0-next.1:
version "1.17.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1"
Expand Down Expand Up @@ -11095,6 +11102,11 @@ stack-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==

stackframe@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==

static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
Expand Down

0 comments on commit 9045686

Please sign in to comment.