diff --git a/doc/api/errors.md b/doc/api/errors.md
index c6c8140e259102..0000e37f6c4387 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -2678,6 +2678,25 @@ An unspecified or non-specific system error has occurred within the Node.js
process. The error object will have an `err.info` object property with
additional details.
+
+
+### `ERR_TAP_VALIDATOR_ERROR`
+
+This error represents a failed TAP validation.
+
+
+
+### `ERR_TAP_LEXER_ERROR`
+
+An error representing a failing lexer state.
+
+
+
+### `ERR_TAP_PARSER_ERROR`
+
+An error representing a failing parser state. Additional information about the token
+causing the error is available via the `cause` property.
+
### `ERR_TEST_FAILURE`
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index de861b813a4fe9..2862196acce9c3 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -60,6 +60,8 @@ const {
URIError,
} = primordials;
+const { console } = require('internal/console/global');
+
const kIsNodeError = Symbol('kIsNodeError');
const isWindows = process.platform === 'win32';
@@ -851,6 +853,22 @@ class AbortError extends Error {
}
}
+// TAP Validation Error used by the TAP checker/parser.
+class TAPValidationError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = 'TAPValidationError';
+ }
+}
+
+// TAP Lexical Error used by the TAP lexer.
+class LexerError extends Error {
+ constructor(message) {
+ super(message);
+ this.name = 'LexerError';
+ }
+}
+
/**
* This creates a generic Node.js error.
*
@@ -1591,6 +1609,62 @@ E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
E('ERR_SYNTHETIC', 'JavaScript Callstack', Error);
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError);
+E('ERR_TAP_LEXER_ERROR', function(errorMsg) {
+ hideInternalStackFrames(this);
+ return errorMsg;
+}, LexerError);
+E('ERR_TAP_PARSER_ERROR', function(errorMsg, details, tokenCausedError, source) {
+ hideInternalStackFrames(this);
+ this.cause = tokenCausedError;
+
+ const COLOR_UNDERLINE = '\u001b[4m';
+ const COLOR_WHITE = '\u001b[30;1m';
+ const COLOR_RED = '\u001b[31m';
+ const COLOR_RESET = '\u001b[0m';
+ const COLOR_YELLOW_BG = '\u001b[43m';
+
+ const { column, line, start, end } = tokenCausedError.location;
+ const indent = column + ('' + line).length + 1;
+
+ const sourceLines = source.split('\n');
+ const sourceLine = sourceLines[line - 1];
+
+ const errorDetails = `${details} at line ${line}, column ${column} (start ${start}, end ${end})`;
+
+ // Highlight the errored token in red
+ const sourceLineWithToken =
+ sourceLine.slice(0, column - 1) +
+ COLOR_UNDERLINE +
+ COLOR_RED +
+ sourceLine.slice(column - 1, column + (tokenCausedError.value.length - 1)) +
+ COLOR_RESET +
+ sourceLine.slice(column + (tokenCausedError.value.length - 1));
+
+ console.error(`\n${COLOR_YELLOW_BG + COLOR_RED}TAP Syntax Error:${COLOR_RESET}`);
+
+ for (let index = 0; index < line - 1; index++) {
+ const leadingZero = index < 9 ? '0' : '';
+ const line = sourceLines[index];
+ console.error(
+ `${COLOR_WHITE}${leadingZero}${
+ index + 1
+ }:${COLOR_RESET} ${line}`
+ );
+ }
+
+ const indentString = ' '.repeat(indent) + (line < 9 ? ' ' : '');
+ console.error(`${COLOR_WHITE}${
+ (line < 9 ? '0' : '') + line
+ }:${COLOR_RESET} ${sourceLineWithToken}
+${COLOR_RED}${indentString}\u2502${COLOR_RESET}
+${COLOR_RED}${indentString}\u2514\u2524${errorMsg}${errorDetails}${COLOR_RESET}
+ `);
+ return errorMsg + errorDetails;
+}, SyntaxError);
+E('ERR_TAP_VALIDATOR_ERROR', function(errorMsg) {
+ hideInternalStackFrames(this);
+ return errorMsg;
+}, TAPValidationError);
E('ERR_TEST_FAILURE', function(error, failureType) {
hideInternalStackFrames(this);
assert(typeof failureType === 'string',
diff --git a/lib/internal/test_runner/tap_checker.js b/lib/internal/test_runner/tap_checker.js
index 92c5829beda4aa..497ff9580a406e 100644
--- a/lib/internal/test_runner/tap_checker.js
+++ b/lib/internal/test_runner/tap_checker.js
@@ -1,12 +1,9 @@
'use strict';
-
-class TAPValidationError extends Error {
- constructor(message) {
- super(message);
- this.name = 'TAPValidationError';
- }
-}
+const { NumberParseInt } = primordials;
+const {
+ codes: { ERR_TAP_VALIDATOR_ERROR },
+} = require('internal/errors');
class TAPValidationStrategy {
validate(ast) {
@@ -22,7 +19,7 @@ class TAPValidationStrategy {
const { documents } = ast.root;
if (documents.length > 1) {
- throw new TAPValidationError('Found more than one TAP documents');
+ throw new ERR_TAP_VALIDATOR_ERROR('Found more than one TAP documents');
}
}
@@ -31,7 +28,7 @@ class TAPValidationStrategy {
// TAP14 specification is compatible with observed behavior of existing TAP13 consumers and producers
if (version !== '14' && version !== '13') {
- throw new TAPValidationError('TAP version should be 14');
+ throw new ERR_TAP_VALIDATOR_ERROR('TAP version should be 14');
}
}
@@ -39,22 +36,22 @@ class TAPValidationStrategy {
const { plan } = ast.root.documents[0];
if (!plan) {
- throw new TAPValidationError('Missing Plan');
+ throw new ERR_TAP_VALIDATOR_ERROR('Missing Plan');
}
if (!plan.start) {
- throw new TAPValidationError('Missing Plan start');
+ throw new ERR_TAP_VALIDATOR_ERROR('Missing Plan start');
}
if (!plan.end) {
- throw new TAPValidationError('Missing Plan end');
+ throw new ERR_TAP_VALIDATOR_ERROR('Missing Plan end');
}
- const planStart = parseInt(plan.start, 10);
- const planEnd = parseInt(plan.end, 10);
+ const planStart = NumberParseInt(plan.start, 10);
+ const planEnd = NumberParseInt(plan.end, 10);
if (planEnd !== 0 && planStart > planEnd) {
- throw new TAPValidationError(
+ throw new ERR_TAP_VALIDATOR_ERROR(
`Plan start ${planStart} is greater than Plan end ${planEnd}`
);
}
@@ -62,11 +59,11 @@ class TAPValidationStrategy {
#validateTestPoints(ast) {
const { tests, plan, bailout } = ast.root.documents[0];
- const planStart = parseInt(plan.start, 10);
- const planEnd = parseInt(plan.end, 10);
+ const planStart = NumberParseInt(plan.start, 10);
+ const planEnd = NumberParseInt(plan.end, 10);
if (planEnd === 0 && tests && tests.length > 0) {
- throw new TAPValidationError(
+ throw new ERR_TAP_VALIDATOR_ERROR(
`Found ${tests.length} Test Point${
tests.length > 1 ? 's' : ''
} but Plan is ${planStart}..0`
@@ -75,21 +72,21 @@ class TAPValidationStrategy {
if (planEnd > 0) {
if (!tests || tests.length === 0) {
- throw new TAPValidationError('Missing Test Points');
+ throw new ERR_TAP_VALIDATOR_ERROR('Missing Test Points');
}
if (!bailout && tests.length !== planEnd) {
- throw new TAPValidationError(
+ throw new ERR_TAP_VALIDATOR_ERROR(
`Test Points count ${tests.length} does not match Plan count ${planEnd}`
);
}
for (let i = 0; i < tests.length; i++) {
const test = tests.at(i);
- const testId = parseInt(test.id, 10);
+ const testId = NumberParseInt(test.id, 10);
if (testId < planStart || testId > planEnd) {
- throw new TAPValidationError(
+ throw new ERR_TAP_VALIDATOR_ERROR(
`Test ${testId} is out of Plan range ${planStart}..${planEnd}`
);
}
@@ -103,7 +100,6 @@ class TAP13ValidationStrategy extends TAPValidationStrategy {}
class TAP14ValidationStrategy extends TAPValidationStrategy {}
class TapChecker {
-
static TAP13 = '13';
static TAP14 = '14';
diff --git a/lib/internal/test_runner/tap_lexer.js b/lib/internal/test_runner/tap_lexer.js
index b374c21ecedc17..1aae7da5b1e814 100644
--- a/lib/internal/test_runner/tap_lexer.js
+++ b/lib/internal/test_runner/tap_lexer.js
@@ -1,5 +1,10 @@
'use strict';
+const { SafeSet, MathMax } = primordials;
+const {
+ codes: { ERR_TAP_LEXER_ERROR },
+} = require('internal/errors');
+
const TokenKind = {
EOF: 'EOF',
EOL: 'EOL',
@@ -31,14 +36,14 @@ class Token {
this.value = value;
this.location = {
line: stream.line,
- column: Math.max(stream.column - ('' + value).length + 1, 1), // 1 based
- start: Math.max(stream.pos - ('' + value).length, 0), // zero based
+ column: MathMax(stream.column - ('' + value).length + 1, 1), // 1 based
+ start: MathMax(stream.pos - ('' + value).length, 0), // zero based
end: stream.pos - 1, // zero based
};
// EOF is a special case
if (value === TokenKind.EOF) {
- const eofPosition = stream.input.length + 1; // we consider EOF to be outside the stream
+ const eofPosition = stream.input.length + 1; // We consider EOF to be outside the stream
this.location.start = eofPosition;
this.location.end = eofPosition;
this.location.column = stream.column + 1; // 1 based
@@ -77,78 +82,10 @@ class InputStream {
return char;
}
-
- // this is an example error output:
- //
- // TAP Syntax Error:
- // 01: abc
- //
- // Diagnostic:
- // 01: abc
- // ┬
- // └┤Expected a valid token, got "abc" at line 1, column 1 (start 0, end 3)
-
- error(message, token) {
- const COLOR_UNDERLINE = '\u001b[4m';
- const COLOR_WHITE = '\u001b[30;1m';
- const COLOR_RED = '\u001b[31m';
- const COLOR_RESET = '\u001b[0m';
- const COLOR_YELLOW_BG = '\u001b[43m';
-
- const { column, line, start, end } = token.location;
- const indent = column + ('' + line).length + 1;
-
- const sourceLines = this.input.split('\n');
- const sourceLine = sourceLines[line - 1];
-
- const errorDetails = `, got "${token.value}" (${token.kind}) at line ${line}, column ${column} (start ${start}, end ${end})`;
-
- // highlight the errored token in red
- const sourceLineWithToken =
- sourceLine.slice(0, column - 1) +
- COLOR_UNDERLINE +
- COLOR_RED +
- sourceLine.slice(column - 1, column + (token.value.length - 1)) +
- COLOR_RESET +
- sourceLine.slice(column + (token.value.length - 1));
-
- console.error(
- `${COLOR_YELLOW_BG + COLOR_RED}TAP Syntax Error:${COLOR_RESET}`
- );
-
- sourceLines.forEach((sourceLine, index) => {
- const leadingZero = index < 9 ? '0' : '';
- if (index === line - 1) {
- console.error(
- `${COLOR_RED}${leadingZero}${index + 1}: ${sourceLine + COLOR_RESET}`
- );
- } else {
- console.error(
- `${COLOR_WHITE}${leadingZero}${
- index + 1
- }:${COLOR_RESET} ${sourceLine}`
- );
- }
- });
-
- console.error(`\n${COLOR_YELLOW_BG + COLOR_RED}Diagnostic:${COLOR_RESET}`);
-
- const indentString = ' '.repeat(indent) + (line < 9 ? ' ' : '');
- console.error(`${COLOR_WHITE}${
- (line < 9 ? '0' : '') + line
- }:${COLOR_RESET} ${sourceLineWithToken}
-${COLOR_RED}${indentString}│${COLOR_RESET}
-${COLOR_RED}${indentString}└┤${message}${errorDetails}${COLOR_RESET}
- `);
-
- throw new SyntaxError(message + errorDetails, {
- cause: token,
- });
- }
}
class TapLexer {
- static Keywords = new Set([
+ static Keywords = new SafeSet([
'TAP',
'version',
'ok',
@@ -182,9 +119,9 @@ class TapLexer {
*scan() {
while (!this.#source.eof()) {
- let token = this.#scanToken();
+ const token = this.#scanToken();
- // remember the last scanned token (except for whitespace)
+ // Remember the last scanned token (except for whitespace)
if (token.kind !== TokenKind.WHITESPACE) {
this.#lastScannedToken = token;
}
@@ -229,7 +166,7 @@ class TapLexer {
return this.#scanLiteral(char);
}
- throw new Error(
+ throw new ERR_TAP_LEXER_ERROR(
`Unexpected character: ${char} at line ${this.#line}, column ${
this.#column
}`
@@ -237,11 +174,11 @@ class TapLexer {
}
#scanEOL(char) {
- // in case of odd number of ESCAPE symbols, we need to clear the remaining
- // escape chars from the stack and start fresh for the next line
+ // In case of odd number of ESCAPE symbols, we need to clear the remaining
+ // escape chars from the stack and start fresh for the next line.
this.#escapeStack = [];
- // we also need to reset the comment flag
+ // We also need to reset the comment flag
this.#isComment = false;
return new Token({
@@ -262,7 +199,7 @@ class TapLexer {
}
#scanEscapeSymbol(char) {
- // if the escape symbol has been escaped (by previous symbol),
+ // If the escape symbol has been escaped (by previous symbol),
// or if the next symbol is a whitespace symbol,
// then consume it as a literal.
if (
@@ -277,7 +214,7 @@ class TapLexer {
});
}
- // otherwise, consume the escape symbol as an escape symbol that should be ignored by the parser
+ // Otherwise, consume the escape symbol as an escape symbol that should be ignored by the parser
// we also need to push the escape symbol to the escape stack
// and consume the next character as a literal (done in the next turn)
this.#escapeStack.push(char);
@@ -297,7 +234,7 @@ class TapLexer {
}
#scanDash(char) {
- // peek next 3 characters and check if it's a YAML start marker
+ // Peek next 3 characters and check if it's a YAML start marker
const marker = char + this.#source.peek() + this.#source.peek(1);
if (this.#isYamlStartSymbol(marker)) {
@@ -330,7 +267,7 @@ class TapLexer {
const lastCharacter = this.#source.peek(-2);
const nextToken = this.#source.peek();
- // if we encounter a hash symbol at the beginning of a line,
+ // If we encounter a hash symbol at the beginning of a line,
// we consider it as a comment
if (!lastCharacter || this.#isEOLSymbol(lastCharacter)) {
this.#isComment = true;
@@ -341,7 +278,7 @@ class TapLexer {
});
}
- // the only valid case where a hash symbol is considered as a hash token
+ // The only valid case where a hash symbol is considered as a hash token
// is when it's preceded by a whitespace symbol and followed by a non-hash symbol
if (
this.#isWhitespaceSymbol(lastCharacter) &&
@@ -367,7 +304,7 @@ class TapLexer {
});
}
- // as a fallback, we consume the hash symbol as a literal
+ // As a fallback, we consume the hash symbol as a literal
return new Token({
kind: TokenKind.LITERAL,
value: char,
@@ -378,8 +315,8 @@ class TapLexer {
#scanLiteral(char) {
let word = char;
while (!this.#source.eof()) {
- let char = this.#source.peek();
- if (this.#isLiteralSymbol(char)) {
+ const nextChar = this.#source.peek();
+ if (this.#isLiteralSymbol(nextChar)) {
word += this.#source.next();
} else {
break;
@@ -469,9 +406,9 @@ class TapLexer {
#scanNumeric(char) {
let number = char;
while (!this.#source.eof()) {
- let char = this.#source.peek();
- if (this.#isNumericSymbol(char)) {
- number += char;
+ const nextChar = this.#source.peek();
+ if (this.#isNumericSymbol(nextChar)) {
+ number += nextChar;
this.#source.next();
} else {
break;
@@ -485,7 +422,7 @@ class TapLexer {
}
#hasTheCurrentCharacterBeenEscaped() {
- // use the escapeStack to keep track of the escape characters
+ // Use the escapeStack to keep track of the escape characters
return this.#escapeStack.length > 0;
}
@@ -502,7 +439,7 @@ class TapLexer {
}
#isSpecialCharacterSymbol(char) {
- // we deliberately do not include "# \ + -"" in this list
+ // We deliberately do not include "# \ + -"" in this list
// these are used for comments/reasons explanations, pragma and escape characters
// whitespace is not included because it is handled separately
return '!"$%&\'()*,./:;<=>?@[]^_`{|}~'.indexOf(char) > -1;
diff --git a/lib/internal/test_runner/tap_parser.js b/lib/internal/test_runner/tap_parser.js
index 182e8b906a50a5..f7529e4d03567d 100644
--- a/lib/internal/test_runner/tap_parser.js
+++ b/lib/internal/test_runner/tap_parser.js
@@ -1,32 +1,38 @@
'use strict';
-/** TAP14 specifications
-
-See https://testanything.org/tap-version-14-specification.html
-
-Note that the following grammar is intended as a rough “pseudocode” guidance.
-It is not strict EBNF:
-
-TAPDocument := Version Plan Body | Version Body Plan
-Version := "TAP version 14\n"
-Plan := "1.." (Number) (" # " Reason)? "\n"
-Body := (TestPoint | BailOut | Pragma | Comment | Anything | Empty | Subtest)*
-TestPoint := ("not ")? "ok" (" " Number)? ((" -")? (" " Description) )? (" " Directive)? "\n" (YAMLBlock)?
-Directive := " # " ("todo" | "skip") (" " Reason)?
-YAMLBlock := " ---\n" (YAMLLine)* " ...\n"
-YAMLLine := " " (YAML)* "\n"
-BailOut := "Bail out!" (" " Reason)? "\n"
-Reason := [^\n]+
-Pragma := "pragma " [+-] PragmaKey "\n"
-PragmaKey := ([a-zA-Z0-9_-])+
-Subtest := ("# Subtest" (": " SubtestName)?)? "\n" SubtestDocument TestPoint
-Comment := ^ (" ")* "#" [^\n]* "\n"
-Empty := [\s\t]* "\n"
-Anything := [^\n]+ "\n"
-
-*/
const { TapLexer, TokenKind } = require('internal/test_runner/tap_lexer');
const { TapChecker } = require('internal/test_runner/tap_checker');
+const {
+ codes: { ERR_TAP_PARSER_ERROR },
+} = require('internal/errors');
+
+/**
+ *
+ * TAP14 specifications
+ *
+ * See https://testanything.org/tap-version-14-specification.html
+ *
+ * Note that the following grammar is intended as a rough "pseudocode" guidance.
+ * It is not strict EBNF:
+ *
+ * TAPDocument := Version Plan Body | Version Body Plan
+ * Version := "TAP version 14\n"
+ * Plan := "1.." (Number) (" # " Reason)? "\n"
+ * Body := (TestPoint | BailOut | Pragma | Comment | Anything | Empty | Subtest)*
+ * TestPoint := ("not ")? "ok" (" " Number)? ((" -")? (" " Description) )? (" " Directive)? "\n" (YAMLBlock)?
+ * Directive := " # " ("todo" | "skip") (" " Reason)?
+ * YAMLBlock := " ---\n" (YAMLLine)* " ...\n"
+ * YAMLLine := " " (YAML)* "\n"
+ * BailOut := "Bail out!" (" " Reason)? "\n"
+ * Reason := [^\n]+
+ * Pragma := "pragma " [+-] PragmaKey "\n"
+ * PragmaKey := ([a-zA-Z0-9_-])+
+ * Subtest := ("# Subtest" (": " SubtestName)?)? "\n" SubtestDocument TestPoint
+ * Comment := ^ (" ")* "#" [^\n]* "\n"
+ * Empty := [\s\t]* "\n"
+ * Anything := [^\n]+ "\n"
+ *
+ */
/**
* An LL(1) parser for TAP14/TAP13.
@@ -46,13 +52,14 @@ class TapParser {
#subtestBlockIndentationFactor = 4;
#yamlIndentationFactor = 2;
- // use this stack to keep track of the beginning of each subtest
- // the top of the stack is the current subtest
- // everytime we enter a subtest, we push a new subtest onto the stack
- // when the stack is empty, we assume all subtests have been terminated
+ // Use this stack to keep track of the beginning of each subtest
+ // the top of the stack is the current subtest.
+ // Everytime we enter a subtest, we push a new subtest onto the stack
+ // when the stack is empty, we assume all subtests have been terminated.
#subtestsStack = [];
constructor(input, { specs = TapChecker.TAP14 } = {}) {
+ this.input = input;
this.#checker = new TapChecker({ specs });
this.#lexer = new TapLexer(input);
this.#tokens = this.#makeChunks(this.#lexer.scan());
@@ -62,6 +69,15 @@ class TapParser {
return this.#checker.check(this.#output);
}
+ #error(message, token, received = null) {
+ throw new ERR_TAP_PARSER_ERROR(
+ message,
+ ', received ' + (received || `"${token.value}" (${token.kind})`),
+ token,
+ this.input
+ );
+ }
+
#peek(shouldSkipBlankTokens = true) {
if (shouldSkipBlankTokens) {
this.#skip(TokenKind.WHITESPACE);
@@ -85,7 +101,7 @@ class TapParser {
return this.#currentToken;
}
- // skip the provided tokens in the current chunk
+ // Skip the provided tokens in the current chunk
#skip(...tokensToSkip) {
let token = this.#tokens[this.#currentTokenChunk][this.#currentTokenIndex];
while (token && tokensToSkip.includes(token.kind)) {
@@ -98,7 +114,8 @@ class TapParser {
const literals = [];
let nextToken = this.#peek(false);
- // read all literal, numeric, whitespace and escape tokens until we hit a different token or reach end of current chunk
+ // Read all literal, numeric, whitespace and escape tokens until we hit a different token
+ // or reach end of current chunk
while (
nextToken &&
[
@@ -112,7 +129,7 @@ class TapParser {
) {
const word = this.#next(false).value;
- // don't output escaped characters
+ // Don't output escaped characters
if (nextToken.kind !== TokenKind.ESCAPE) {
literals.push(word);
}
@@ -123,7 +140,7 @@ class TapParser {
return literals.join('');
}
- // split all tokens by EOL: [[token1, token2, ...], [token1, token2, ...]]
+ // Split all tokens by EOL: [[token1, token2, ...], [token1, token2, ...]]
// this would simplify the parsing logic
#makeChunks(tokens) {
return [...tokens]
@@ -141,31 +158,27 @@ class TapParser {
.filter((chunk) => chunk.length > 0 && chunk[0].kind !== TokenKind.EOF);
}
- #countSpaces() {
+ #countNextSpaces() {
const chunk = this.#tokens[this.#currentTokenChunk];
- // count the number of whitespace tokens in the chunk, starting from the first token
+ // Count the number of whitespace tokens in the chunk, starting from the first token
let whitespaceCount = 0;
- for (let i = 0; i < chunk.length; i++) {
- if (chunk[i].kind === TokenKind.WHITESPACE) {
- whitespaceCount++;
- } else {
- break;
- }
+ while (chunk[whitespaceCount].kind === TokenKind.WHITESPACE) {
+ whitespaceCount++;
}
return whitespaceCount;
}
- #getIndentationLevel(chunk, indentationFactor = 2) {
- const whitespaceCount = this.#countSpaces();
+ #getCurrentIndentationLevel(indentationFactor = 2) {
+ const whitespaceCount = this.#countNextSpaces();
- // if the number of whitespace tokens is an exact multiple of "indentationFactor"
+ // If the number of whitespace tokens is an exact multiple of "indentationFactor"
// then return the level of the indentation
if (whitespaceCount % indentationFactor === 0) {
return whitespaceCount / indentationFactor;
}
- // take into account any extra 2 whitespaces related to a YAML block
+ // Take into account any extra 2 whitespaces related to a YAML block
if (
(whitespaceCount - this.#yamlIndentationFactor) % indentationFactor ===
0
@@ -179,7 +192,7 @@ class TapParser {
return 0;
}
- // run a depth-first traversal of each node in the current AST
+ // Run a depth-first traversal of each node in the current AST
// until we visit a certain indentation level.
// we also create a new "documents" entry for each level we visit
// this will be used to host the coming subtest entries
@@ -195,42 +208,44 @@ class TapParser {
}
}
- //----------------------------------------------------------------------//
- //------------------------------ Visitors ------------------------------//
- //----------------------------------------------------------------------//
+ // ----------------------------------------------------------------------//
+ // ------------------------------ Visitors ------------------------------//
+ // ----------------------------------------------------------------------//
#visitTestPoint(value) {
this.#visit(this.#documents, this.#subTestLevel, (node) => {
- // if we are at the parent level, check if the current test is terminating any
+ // If we are at the parent level, check if the current test is terminating any
// subtests that are still open
if (this.#subtestsStack.length > 0) {
- // peek most recent subtest on the stack
+ // Peek most recent subtest on the stack
const { name, level } = this.#subtestsStack.at(-1);
- // if the current test is terminating a subtest, then we need to close it
+ // If the current test is terminating a subtest, then we need to close it
+ /* eslint-disable brace-style */
if (level === this.#subTestLevel + 1 && name === value.description) {
- // terminate the most recent subtest
+ // Terminate the most recent subtest
this.#subtestsStack.pop();
- // mark the subtest as terminated in the most recent child document
+ // Mark the subtest as terminated in the most recent child document
node.documents.at(-1).documents.at(-1).terminated = true;
- // create a sub documents entry in current documents for the next subtest (if any)
+ // Create a sub documents entry in current documents for the next subtest (if any)
// this will allow us to make sure any new subtests are created in the new document (context)
node.documents.at(-1).documents.push({});
- // add the test point entry to the most recent parent document
+ // Add the test point entry to the most recent parent document
node.documents.at(-1).tests ||= [];
node.documents.at(-1).tests.push(value);
+
}
- // if no subtest is terminating, then we need to add the test point to the most recent subtest
+ // If no subtest is terminating, then we need to add the test point to the most recent subtest
else {
node.documents.at(-1).tests ||= [];
node.documents.at(-1).tests.push(value);
}
}
- // if no subtests were parsed, then we need to add the test point to the most recent document
+ // If no subtests were parsed, then we need to add the test point to the most recent document
// this is the case when we are parsing a test that is at the root level
else {
node.documents.at(-1).tests ||= [];
@@ -266,14 +281,14 @@ class TapParser {
node.documents.at(-1).name = value;
node.documents.at(-1).terminated = false;
- // we store the name of the coming subtest, and its level.
+ // We store the name of the coming subtest, and its level.
// the subtest level is the level of the current indentation level + 1
this.#subtestsStack.push({
name: value,
level: this.#subTestLevel + 1,
});
},
- // subtest name declared in comment is usually encountered before the subtest block starts.
+ // Subtest name declared in comment is usually encountered before the subtest block starts.
// we need to emit the name on the node that comes after the current node.
// this is why we set the stop level to -1.
// This will allow us to create a new document node for the coming subtest.
@@ -307,14 +322,13 @@ class TapParser {
// TAPDocument := Version Plan Body | Version Body Plan
parse() {
this.#tokens.forEach((chunk) => {
- this.#subTestLevel = this.#getIndentationLevel(
- chunk,
+ this.#subTestLevel = this.#getCurrentIndentationLevel(
this.#subtestBlockIndentationFactor
);
- // if the chunk starts with a multiple of 4 whitespace tokens,
+ // If the chunk starts with a multiple of 4 whitespace tokens,
// then it's a subtest
if (this.#subTestLevel > 0) {
- // remove the indentation block from the current chunk
+ // Remove the indentation block from the current chunk
// but only if the chunk is not a YAML block
if (!this.#isYAMLBlock) {
@@ -326,17 +340,14 @@ class TapParser {
this.#TAPDocument(chunk);
- // move pointers to the next chunk and reset the current token index
+ // Move pointers to the next chunk and reset the current token index
this.#currentTokenChunk++;
this.#currentTokenIndex = 0;
});
if (this.#isYAMLBlock) {
- // looks like we have a non-ending YAML block
- this.#lexer.error(
- `Expected end of YAML block`,
- this.#tokens.at(-1).at(-1)
- );
+ // Looks like we have a non-ending YAML block
+ this.#error('Expected end of YAML block', this.#tokens.at(-1).at(-1));
}
this.#output = {
@@ -346,20 +357,20 @@ class TapParser {
return this.#output;
}
- //--------------------------------------------------------------------------//
- //------------------------------ Parser rules ------------------------------//
- //--------------------------------------------------------------------------//
+ // --------------------------------------------------------------------------//
+ // ------------------------------ Parser rules ------------------------------//
+ // --------------------------------------------------------------------------//
#TAPDocument(chunk) {
const chunkAsString = chunk.map((token) => token.value).join('');
const firstToken = chunk[0];
const { kind } = firstToken;
- // only even number of spaces are considered indentation
- const nbSpaces = this.#countSpaces();
- if (nbSpaces % 2 !== 0) {
- this.#lexer.error(`Expected ${nbSpaces - 1} spaces`, chunk[nbSpaces - 1]);
- }
+ // // Only even number of spaces are considered indentation
+ // const nbSpaces = this.#countSpaces();
+ // if (nbSpaces % 2 !== 0) {
+ // this.#error(`Expected ${nbSpaces - 1} spaces`, chunk[nbSpaces - 1]);
+ // }
switch (kind) {
case TokenKind.TAP:
@@ -376,12 +387,15 @@ class TapParser {
case TokenKind.WHITESPACE:
return this.#YAMLBlock();
case TokenKind.LITERAL:
- // check for "Bail out!" literal (case insensitive)
+ // Check for "Bail out!" literal (case insensitive)
if (/^Bail\s+out!/i.test(chunkAsString)) {
return this.#Bailout();
}
+ this.#error('Expected a valid token', firstToken);
+
+ break;
default:
- this.#lexer.error(`Expected a valid token`, firstToken);
+ this.#error('Expected a valid token', firstToken);
}
}
@@ -391,17 +405,17 @@ class TapParser {
const tapToken = this.#next();
if (tapToken.kind !== TokenKind.TAP) {
- this.#lexer.error(`Expected "TAP" keyword`, tapToken);
+ this.#error('Expected "TAP" keyword', tapToken);
}
const versionToken = this.#next();
if (versionToken.kind !== TokenKind.TAP_VERSION) {
- this.#lexer.error(`Expected "version" keyword`, versionToken);
+ this.#error('Expected "version" keyword', versionToken);
}
const numberToken = this.#next();
if (numberToken.kind !== TokenKind.NUMERIC) {
- this.#lexer.error(`Expected a version number`, numberToken);
+ this.#error('Expected a version number', numberToken);
}
this.#visitVersion(numberToken.value);
@@ -410,21 +424,22 @@ class TapParser {
// ----------------Plan----------------
// Plan := "1.." (Number) (" # " Reason)? "\n"
#Plan() {
- // even if specs mention plan starts at 1, we need to make sure we read the plan start value in case of a missing or invalid plan start value
+ // Even if specs mention plan starts at 1, we need to make sure we read the plan start value
+ // in case of a missing or invalid plan start value
const planStart = this.#next();
if (planStart.kind !== TokenKind.NUMERIC) {
- this.#lexer.error(`Expected a plan start count`, planStart);
+ this.#error('Expected a plan start count', planStart);
}
const planToken = this.#next();
if (planToken.kind !== TokenKind.TAP_PLAN) {
- this.#lexer.error(`Expected ".." symbol`, planToken);
+ this.#error('Expected ".." symbol', planToken);
}
const planEnd = this.#next();
if (planEnd.kind !== TokenKind.NUMERIC) {
- this.#lexer.error(`Expected a plan end count`, planEnd);
+ this.#error('Expected a plan end count', planEnd);
}
const body = {
@@ -439,7 +454,7 @@ class TapParser {
this.#next(); // skip hash
body.reason = this.#readNextLiterals().trim();
} else if (hashToken.kind === TokenKind.LITERAL) {
- this.#lexer.error(`Expected "#" symbol before a reason`, hashToken);
+ this.#error('Expected "#" symbol before a reason', hashToken);
}
}
@@ -467,7 +482,7 @@ class TapParser {
const okToken = this.#next();
if (okToken.kind !== TokenKind.TAP_TEST_OK) {
- this.#lexer.error(`Expected "ok" or "not ok" keyword`, okToken);
+ this.#error('Expected "ok" or "not ok" keyword', okToken);
}
// Read optional test number
@@ -475,11 +490,11 @@ class TapParser {
if (numberToken && numberToken.kind === TokenKind.NUMERIC) {
numberToken = this.#next().value;
} else {
- numberToken = ''; // set an empty ID to indicate that the test hasn't provider an ID
+ numberToken = ''; // Set an empty ID to indicate that the test hasn't provider an ID
}
const body = {
- // output both failed and passed properties to make it easier for the checker to detect the test status
+ // Output both failed and passed properties to make it easier for the checker to detect the test status
status: {
fail: isTestFailed,
pass: !isTestFailed,
@@ -554,7 +569,7 @@ class TapParser {
#Comment() {
const commentToken = this.#next();
if (commentToken.kind !== TokenKind.COMMENT) {
- this.#lexer.error(`Expected "#" symbol`, commentToken);
+ this.#error('Expected "#" symbol', commentToken);
}
const subtestKeyword = this.#peek();
@@ -585,38 +600,38 @@ class TapParser {
const yamlBlockToken = this.#peek(false);
if (yamlBlockToken.kind === TokenKind.TAP_YAML_START) {
if (this.#isYAMLBlock) {
- // looks like we have another YAML start block, but we didn't close the previous one
- this.#lexer.error(`Unexpected YAML start marker`, yamlBlockToken);
+ // Looks like we have another YAML start block, but we didn't close the previous one
+ this.#error('Unexpected YAML start marker', yamlBlockToken);
}
this.#isYAMLBlock = true;
this.#next(false); // skip "---"
} else if (yamlBlockToken.kind === TokenKind.TAP_YAML_END) {
if (!this.#isYAMLBlock) {
- // looks like we have an YAML end block, but we didn't encouter any YAML start marker
- this.#lexer.error(`Unexpected YAML end marker`, yamlBlockToken);
+ // Looks like we have an YAML end block, but we didn't encouter any YAML start marker
+ this.#error('Unexpected YAML end marker', yamlBlockToken);
}
this.#isYAMLBlock = false;
this.#next(false); // skip "..."
this.#visitYAMLBlock(this.#yamlBlockBuffer);
+ } else if (this.#isYAMLBlock) {
+ // We are in a YAML block, read content as is and store it in the buffer
+ // TODO(@manekinekko): should we use a YAML parser here?
+ this.#yamlBlockBuffer.push(this.#readNextLiterals());
} else {
- if (this.#isYAMLBlock) {
- // check if this is a valid indentation level
- const spaces = this.#countSpaces();
- const validaYAMLIndentationLevel =
- this.#subTestLevel * this.#subtestBlockIndentationFactor +
- this.#yamlIndentationFactor;
-
- if (validaYAMLIndentationLevel !== spaces) {
- this.#lexer.error(
- `Expected valid YAML indentation level`,
- this.#tokens[this.#currentTokenChunk][spaces - 1]
- );
- }
-
- this.#yamlBlockBuffer.push(this.#readNextLiterals());
+ // Check if this is a valid indentation level
+ const spacesFound = this.#countNextSpaces();
+
+ if (spacesFound !== this.#yamlIndentationFactor) {
+ this.#error(
+ `Expected valid YAML indentation (${
+ this.#yamlIndentationFactor
+ } spaces)`,
+ this.#tokens[this.#currentTokenChunk][spacesFound - 1],
+ `${spacesFound} space` + (spacesFound > 1 ? 's' : '')
+ );
}
}
}
@@ -627,7 +642,7 @@ class TapParser {
#Pragma() {
const pragmaToken = this.#next();
if (pragmaToken.kind !== TokenKind.TAP_PRAGMA) {
- this.#lexer.error(`Expected "pragma" keyword`, pragmaToken);
+ this.#error('Expected "pragma" keyword', pragmaToken);
}
const pragmas = {};
@@ -644,20 +659,17 @@ class TapParser {
} else if (pragmaKeySign.kind === TokenKind.DASH) {
isEnabled = false;
} else {
- this.#lexer.error(
- `Expected "+" or "-" before pragma keys`,
- pragmaKeySign
- );
+ this.#error('Expected "+" or "-" before pragma keys', pragmaKeySign);
}
const pragmaKeyToken = this.#peek();
if (pragmaKeyToken.kind !== TokenKind.LITERAL) {
- this.#lexer.error(`Expected pragma key`, pragmaKeyToken);
+ this.#error('Expected pragma key', pragmaKeyToken);
}
let pragmaKey = this.#next().value;
- // in some cases, pragma key can be followed by a comma separator,
+ // In some cases, pragma key can be followed by a comma separator,
// so we need to remove it
pragmaKey = pragmaKey.replace(/,/g, '');
diff --git a/test/parallel/test-runner-tap-checker.js b/test/parallel/test-runner-tap-checker.js
index 022db3b733b01a..924addcfc3ab4e 100644
--- a/test/parallel/test-runner-tap-checker.js
+++ b/test/parallel/test-runner-tap-checker.js
@@ -15,9 +15,9 @@ function TAPChecker(input) {
}
{
- assert.throws(() => TAPChecker(`TAP version 14`), {
+ assert.throws(() => TAPChecker('TAP version 14'), {
name: 'TAPValidationError',
- message: `Missing Plan`,
+ message: 'Missing Plan',
});
}
@@ -30,19 +30,19 @@ TAP version 14
`),
{
name: 'TAPValidationError',
- message: `Missing Test Points`,
+ message: 'Missing Test Points',
}
);
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+
+ // Valid TAP14 should not throw
+ TAPChecker(`
TAP version 14
1..1
ok
-`)
- );
+`);
}
{
@@ -55,7 +55,7 @@ ok 2
`),
{
name: 'TAPValidationError',
- message: `Test 2 is out of Plan range 1..1`,
+ message: 'Test 2 is out of Plan range 1..1',
}
);
}
@@ -70,7 +70,7 @@ ok 2
`),
{
name: 'TAPValidationError',
- message: `Plan start 3 is greater than Plan end 1`,
+ message: 'Plan start 3 is greater than Plan end 1',
}
);
}
@@ -87,14 +87,14 @@ ok 3
`),
{
name: 'TAPValidationError',
- message: `Test 1 is out of Plan range 2..3`,
+ message: 'Test 1 is out of Plan range 2..3',
}
);
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+// Valid comment line shout not throw.
+ TAPChecker(`
TAP version 14
1..5
ok 1 - approved operating system
@@ -103,35 +103,33 @@ ok 2 - # SKIP no /sys directory
ok 3 - # SKIP no /sys directory
ok 4 - # SKIP no /sys directory
ok 5 - # SKIP no /sys directory
-`)
- );
+`);
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+// Valida empty test plan should not throw.
+ TAPChecker(`
TAP version 14
1..0 # skip because English-to-French translator isn't installed
-`)
- );
+`);
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+// Valid test plan count should not throw.
+ TAPChecker(`
TAP version 14
1..4
ok 1 - Creating test program
ok 2 - Test program runs, no error
not ok 3 - infinite loop # TODO halting problem unsolved
not ok 4 - infinite loop 2 # TODO halting problem unsolved
-`)
- );
+`);
+
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+// Valid YAML diagnostic should not throw.
+ TAPChecker(`
TAP version 14
ok - created Board
ok
@@ -158,17 +156,15 @@ ok
...
ok - board has 7 tiles + starter tile
1..9
-`)
- );
+`);
}
{
- assert.doesNotThrow(() =>
- TAPChecker(`
+ // Valid Bail out should not throw.
+ TAPChecker(`
TAP version 14
1..573
not ok 1 - database handle
Bail out! Couldn't connect to database.
-`)
- );
+`);
}
diff --git a/test/parallel/test-runner-tap-lexer.js b/test/parallel/test-runner-tap-lexer.js
index ce22adab48fe77..47237a8872aeed 100644
--- a/test/parallel/test-runner-tap-lexer.js
+++ b/test/parallel/test-runner-tap-lexer.js
@@ -12,7 +12,7 @@ function TAPLexer(input) {
}
{
- const tokens = TAPLexer(`TAP version 14`);
+ const tokens = TAPLexer('TAP version 14');
assert.strictEqual(tokens.length, 6);
@@ -30,7 +30,7 @@ function TAPLexer(input) {
}
{
- const tokens = TAPLexer(`1..5 # reason`);
+ const tokens = TAPLexer('1..5 # reason');
[
{ kind: TokenKind.NUMERIC, value: '1' },
@@ -49,7 +49,7 @@ function TAPLexer(input) {
{
const tokens = TAPLexer(
- `1..5 # reason "\\ !"\\#$%&'()*+,\\-./:;<=>?@[]^_\`{|}~`
+ '1..5 # reason "\\ !"\\#$%&\'()*+,\\-./:;<=>?@[]^_`{|}~'
);
[
@@ -81,7 +81,7 @@ function TAPLexer(input) {
}
{
- const tokens = TAPLexer(`ok`);
+ const tokens = TAPLexer('ok');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -93,7 +93,7 @@ function TAPLexer(input) {
}
{
- const tokens = TAPLexer(`not ok`);
+ const tokens = TAPLexer('not ok');
[
{ kind: TokenKind.TAP_TEST_NOTOK, value: 'not' },
@@ -107,7 +107,7 @@ function TAPLexer(input) {
}
{
- const tokens = TAPLexer(`ok 1`);
+ const tokens = TAPLexer('ok 1');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -173,7 +173,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 description`);
+ const tokens = TAPLexer('ok 1 description');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -189,7 +189,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 - description`);
+ const tokens = TAPLexer('ok 1 - description');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -207,7 +207,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 - description # todo`);
+ const tokens = TAPLexer('ok 1 - description # todo');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -229,7 +229,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 - description \\# todo`);
+ const tokens = TAPLexer('ok 1 - description \\# todo');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -252,7 +252,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 - description \\ # todo`);
+ const tokens = TAPLexer('ok 1 - description \\ # todo');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -277,7 +277,7 @@ ok 1
{
const tokens = TAPLexer(
- `ok 1 description \\# \\\\ world # TODO escape \\# characters with \\\\`
+ 'ok 1 description \\# \\\\ world # TODO escape \\# characters with \\\\'
);
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -317,7 +317,7 @@ ok 1
}
{
- const tokens = TAPLexer(`ok 1 - description # ##`);
+ const tokens = TAPLexer('ok 1 - description # ##');
[
{ kind: TokenKind.TAP_TEST_OK, value: 'ok' },
@@ -340,7 +340,7 @@ ok 1
}
{
- const tokens = TAPLexer(`# comment`);
+ const tokens = TAPLexer('# comment');
[
{ kind: TokenKind.COMMENT, value: '#' },
{ kind: TokenKind.WHITESPACE, value: ' ' },
@@ -353,7 +353,7 @@ ok 1
}
{
- const tokens = TAPLexer(`#`);
+ const tokens = TAPLexer('#');
[
{ kind: TokenKind.COMMENT, value: '#' },
@@ -406,7 +406,7 @@ ok 1
}
{
- const tokens = TAPLexer(`pragma +strict -warnings`);
+ const tokens = TAPLexer('pragma +strict -warnings');
[
{ kind: TokenKind.TAP_PRAGMA, value: 'pragma' },
@@ -424,7 +424,7 @@ ok 1
}
{
- const tokens = TAPLexer(`Bail out! Error`);
+ const tokens = TAPLexer('Bail out! Error');
[
{ kind: TokenKind.LITERAL, value: 'Bail' },
diff --git a/test/parallel/test-runner-tap-parser.js b/test/parallel/test-runner-tap-parser.js
index f5dec03049d08a..a5359ccc71edde 100644
--- a/test/parallel/test-runner-tap-parser.js
+++ b/test/parallel/test-runner-tap-parser.js
@@ -3,7 +3,6 @@
require('../common');
const assert = require('assert');
-const util = require('util');
const { TapParser } = require('internal/test_runner/tap_parser');
@@ -16,7 +15,7 @@ function TAPParser(input) {
// TAP version
{
- const ast = TAPParser(`TAP version 14`);
+ const ast = TAPParser('TAP version 14');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -29,25 +28,25 @@ function TAPParser(input) {
}
{
- assert.throws(() => TAPParser(`TAP version`), {
+ assert.throws(() => TAPParser('TAP version'), {
name: 'SyntaxError',
message:
- 'Expected a version number, got "EOF" (EOF) at line 1, column 12 (start 12, end 12)',
+ 'Expected a version number, received "EOF" (EOF) at line 1, column 12 (start 12, end 12)',
});
}
{
- assert.throws(() => TAPParser(`TAP`), {
+ assert.throws(() => TAPParser('TAP'), {
name: 'SyntaxError',
message:
- 'Expected "version" keyword, got "EOF" (EOF) at line 1, column 4 (start 4, end 4)',
+ 'Expected "version" keyword, received "EOF" (EOF) at line 1, column 4 (start 4, end 4)',
});
}
// Test plan
{
- const ast = TAPParser(`1..5 # reason`);
+ const ast = TAPParser('1..5 # reason');
assert.deepStrictEqual(ast, {
root: {
documents: [{ plan: { start: '1', end: '5', reason: 'reason' } }],
@@ -57,7 +56,7 @@ function TAPParser(input) {
{
const ast = TAPParser(
- `1..5 # reason "\\ !"\\#$%&'()*+,\\-./:;<=>?@[]^_\`{|}~`
+ '1..5 # reason "\\ !"\\#$%&\'()*+,\\-./:;<=>?@[]^_`{|}~'
);
assert.deepStrictEqual(ast, {
root: {
@@ -75,41 +74,41 @@ function TAPParser(input) {
}
{
- assert.throws(() => TAPParser(`1..`), {
+ assert.throws(() => TAPParser('1..'), {
name: 'SyntaxError',
message:
- 'Expected a plan end count, got "EOF" (EOF) at line 1, column 4 (start 4, end 4)',
+ 'Expected a plan end count, received "EOF" (EOF) at line 1, column 4 (start 4, end 4)',
});
}
{
- assert.throws(() => TAPParser(`1..abc`), {
+ assert.throws(() => TAPParser('1..abc'), {
name: 'SyntaxError',
message:
- 'Expected ".." symbol, got "..abc" (Literal) at line 1, column 2 (start 1, end 5)',
+ 'Expected ".." symbol, received "..abc" (Literal) at line 1, column 2 (start 1, end 5)',
});
}
{
- assert.throws(() => TAPParser(`1..-1`), {
+ assert.throws(() => TAPParser('1..-1'), {
name: 'SyntaxError',
message:
- 'Expected a plan end count, got "-" (Dash) at line 1, column 4 (start 3, end 3)',
+ 'Expected a plan end count, received "-" (Dash) at line 1, column 4 (start 3, end 3)',
});
}
{
- assert.throws(() => TAPParser(`1.1`), {
+ assert.throws(() => TAPParser('1.1'), {
name: 'SyntaxError',
message:
- 'Expected ".." symbol, got "." (Literal) at line 1, column 2 (start 1, end 1)',
+ 'Expected ".." symbol, received "." (Literal) at line 1, column 2 (start 1, end 1)',
});
}
// Test point
{
- const ast = TAPParser(`ok`);
+ const ast = TAPParser('ok');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -134,7 +133,7 @@ function TAPParser(input) {
}
{
- const ast = TAPParser(`not ok`);
+ const ast = TAPParser('not ok');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -159,7 +158,7 @@ function TAPParser(input) {
}
{
- const ast = TAPParser(`ok 1`);
+ const ast = TAPParser('ok 1');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -223,7 +222,7 @@ not ok 2
}
{
- // a subtest is indented by 4 spaces
+ // A subtest is indented by 4 spaces
const ast = TAPParser(`
ok 1
ok 1
@@ -269,7 +268,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 description`);
+ const ast = TAPParser('ok 1 description');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -294,7 +293,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 - description`);
+ const ast = TAPParser('ok 1 - description');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -319,7 +318,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 - description # todo`);
+ const ast = TAPParser('ok 1 - description # todo');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -344,7 +343,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 - description \\# todo`);
+ const ast = TAPParser('ok 1 - description \\# todo');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -369,7 +368,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 - description \\ # todo`);
+ const ast = TAPParser('ok 1 - description \\ # todo');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -395,7 +394,7 @@ ok 1
{
const ast = TAPParser(
- `ok 1 description \\# \\\\ world # TODO escape \\# characters with \\\\`
+ 'ok 1 description \\# \\\\ world # TODO escape \\# characters with \\\\'
);
assert.deepStrictEqual(ast, {
root: {
@@ -421,7 +420,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1 - description # ##`);
+ const ast = TAPParser('ok 1 - description # ##');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -447,7 +446,7 @@ ok 1
{
const ast = TAPParser(
- `ok 2 not skipped: https://example.com/page.html#skip is a url`
+ 'ok 2 not skipped: https://example.com/page.html#skip is a url'
);
assert.deepStrictEqual(ast, {
root: {
@@ -474,7 +473,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 3 - #SkIp case insensitive, so this is skipped`);
+ const ast = TAPParser('ok 3 - #SkIp case insensitive, so this is skipped');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -499,7 +498,7 @@ ok 1
}
{
- const ast = TAPParser(`ok ok ok`);
+ const ast = TAPParser('ok ok ok');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -524,7 +523,7 @@ ok 1
}
{
- const ast = TAPParser(`ok not ok`);
+ const ast = TAPParser('ok not ok');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -549,7 +548,7 @@ ok 1
}
{
- const ast = TAPParser(`ok 1..1`);
+ const ast = TAPParser('ok 1..1');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -563,7 +562,7 @@ ok 1
skip: false,
todo: false,
},
- description: '', // this looks like an edge case
+ description: '', // This looks like an edge case
reason: '',
},
],
@@ -576,7 +575,7 @@ ok 1
// Comment
{
- const ast = TAPParser(`# comment`);
+ const ast = TAPParser('# comment');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -589,7 +588,7 @@ ok 1
}
{
- const ast = TAPParser(`#`);
+ const ast = TAPParser('#');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -602,7 +601,7 @@ ok 1
}
{
- const ast = TAPParser(`####`);
+ const ast = TAPParser('####');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -617,12 +616,46 @@ ok 1
// Diagnostic
{
- // note the leading 2 valid spaces
+ // Note the leading 2 valid spaces
const ast = TAPParser(`
---
message: 'description'
property: 'value'
...
+`);
+ assert.deepStrictEqual(ast, {
+ root: {
+ documents: [
+ {
+ tests: [
+ {
+ diagnostics: ["message: 'description'", "property: 'value'"],
+ },
+ ],
+ },
+ ],
+ },
+ });
+}
+
+{
+ // Note the leading 2 valid spaces
+ const ast = TAPParser(`
+ ---
+ message: "Board layout"
+ severity: comment
+ dump:
+ board:
+ - ' 16G 05C '
+ - ' G N C C C G '
+ - ' G C + '
+ - '10C 01G 03C '
+ - 'R N G G A G C C C '
+ - ' R G C + '
+ - ' 01G 17C 00C '
+ - ' G A G G N R R N R '
+ - ' G R G '
+ ...
`);
assert.deepStrictEqual(ast, {
root: {
@@ -631,8 +664,19 @@ ok 1
tests: [
{
diagnostics: [
- "message: 'description'",
- "property: 'value'",
+ 'message: "Board layout"',
+ 'severity: comment',
+ 'dump:',
+ ' board:',
+ " - ' 16G 05C '",
+ " - ' G N C C C G '",
+ " - ' G C + '",
+ " - '10C 01G 03C '",
+ " - 'R N G G A G C C C '",
+ " - ' R G C + '",
+ " - ' 01G 17C 00C '",
+ " - ' G A G G N R R N R '",
+ " - ' G R G '",
],
},
],
@@ -675,7 +719,7 @@ ok 1
{
name: 'SyntaxError',
message:
- 'Unexpected YAML end marker, got "..." (YamlEndKeyword) at line 4, column 3 (start 48, end 50)',
+ 'Unexpected YAML end marker, received "..." (YamlEndKeyword) at line 4, column 3 (start 48, end 50)',
}
);
}
@@ -692,7 +736,8 @@ ok 1
),
{
name: 'SyntaxError',
- message: `Expected end of YAML block, got "'value'" (Literal) at line 4, column 13 (start 44, end 50)`,
+ message:
+ 'Expected end of YAML block, received "\'value\'" (Literal) at line 4, column 13 (start 44, end 50)',
}
);
}
@@ -700,7 +745,7 @@ ok 1
{
assert.throws(
() =>
- // note the leading 3 spaces before ---
+ // Note the leading 3 spaces before ---
TAPParser(
`
---
@@ -711,7 +756,8 @@ ok 1
),
{
name: 'SyntaxError',
- message: `Expected 2 spaces, got " " (Whitespace) at line 2, column 3 (start 3, end 3)`,
+ message:
+ 'Expected valid YAML indentation (2 spaces), received 3 spaces at line 2, column 3 (start 3, end 3)',
}
);
}
@@ -719,7 +765,7 @@ ok 1
{
assert.throws(
() =>
- // note the leading 5 spaces before ---
+ // Note the leading 5 spaces before ---
TAPParser(
`
---
@@ -730,7 +776,8 @@ ok 1
),
{
name: 'SyntaxError',
- message: `Expected 4 spaces, got " " (Whitespace) at line 2, column 5 (start 5, end 5)`,
+ message:
+ 'Expected valid YAML indentation (2 spaces), received 5 spaces at line 2, column 5 (start 5, end 5)',
}
);
}
@@ -738,7 +785,7 @@ ok 1
{
assert.throws(
() =>
- // note the leading 4 spaces before ---
+ // Note the leading 4 spaces before ---
TAPParser(
`
---
@@ -749,7 +796,8 @@ ok 1
),
{
name: 'SyntaxError',
- message: `Expected a valid token, got "---" (YamlStartKeyword) at line 2, column 5 (start 5, end 7)`,
+ message:
+ 'Expected a valid token, received "---" (YamlStartKeyword) at line 2, column 5 (start 5, end 7)',
}
);
}
@@ -757,7 +805,7 @@ ok 1
{
assert.throws(
() =>
- // note the leading 4 spaces before ...
+ // Note the leading 4 spaces before ...
TAPParser(
`
---
@@ -768,7 +816,8 @@ ok 1
),
{
name: 'SyntaxError',
- message: `Expected valid YAML indentation level, got " " (Whitespace) at line 5, column 4 (start 55, end 55)`,
+ message:
+ 'Expected end of YAML block, received "..." (YamlEndKeyword) at line 5, column 5 (start 56, end 58)',
}
);
}
@@ -776,7 +825,7 @@ ok 1
// Pragma
{
- const ast = TAPParser(`pragma +strict, -warnings`);
+ const ast = TAPParser('pragma +strict, -warnings');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -794,7 +843,7 @@ ok 1
// Bail out
{
- const ast = TAPParser(`Bail out! Error`);
+ const ast = TAPParser('Bail out! Error');
assert.deepStrictEqual(ast, {
root: {
documents: [
@@ -809,17 +858,17 @@ ok 1
// Non-recognized
{
- assert.throws(() => TAPParser(`abc`), {
+ assert.throws(() => TAPParser('abc'), {
name: 'SyntaxError',
message:
- 'Expected a valid token, got "abc" (Literal) at line 1, column 1 (start 0, end 2)',
+ 'Expected a valid token, received "abc" (Literal) at line 1, column 1 (start 0, end 2)',
});
}
{
- assert.throws(() => TAPParser(` abc`), {
+ assert.throws(() => TAPParser(' abc'), {
name: 'SyntaxError',
message:
- 'Expected a valid token, got "abc" (Literal) at line 1, column 5 (start 4, end 6)',
+ 'Expected a valid token, received "abc" (Literal) at line 1, column 5 (start 4, end 6)',
});
}