From a94927ee70d2188e2ee6f986898ee4cefd21e3c7 Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Wed, 13 Jul 2022 18:16:42 +0200 Subject: [PATCH] test_runner: fix linting issues --- doc/api/errors.md | 19 ++ lib/internal/errors.js | 74 +++++++ lib/internal/test_runner/tap_checker.js | 42 ++-- lib/internal/test_runner/tap_lexer.js | 119 +++-------- lib/internal/test_runner/tap_parser.js | 248 ++++++++++++----------- test/parallel/test-runner-tap-checker.js | 56 +++-- test/parallel/test-runner-tap-lexer.js | 34 ++-- test/parallel/test-runner-tap-parser.js | 159 ++++++++++----- 8 files changed, 417 insertions(+), 334 deletions(-) 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)', }); }