diff --git a/changelogs/fragments/38-helpful-errors.yml b/changelogs/fragments/38-helpful-errors.yml new file mode 100644 index 0000000..8d52692 --- /dev/null +++ b/changelogs/fragments/38-helpful-errors.yml @@ -0,0 +1,4 @@ +minor_changes: + - "Can switch between error messages containing a shortened version of the faulty markup or the full faulty markup command (https://github.com/ansible-community/antsibull-docs-ts/pull/38)." +breaking_changes: + - "By default, the error messages now contain the full faulty markup command (https://github.com/ansible-community/antsibull-docs-ts/pull/38)." diff --git a/src/opts.ts b/src/opts.ts index fa29c8e..2ffae7e 100644 --- a/src/opts.ts +++ b/src/opts.ts @@ -49,8 +49,11 @@ export interface ParsingOptions extends ErrorHandlingOptions { /** If set to 'true', only 'classic' Ansible docs markup is accepted. */ onlyClassicMarkup?: boolean; - /** If set to 'true', add source information to every part ('source' attribute). */ + /** If set to 'true' (default is 'false'), add source information to every part ('source' attribute). */ addSource?: boolean; + + /** If set to 'true' (default is 'true'), include the faulty markup in error messages. */ + helpfulErrors?: boolean; } export interface CommonExportOptions extends ErrorHandlingOptions { diff --git a/src/parser.spec.ts b/src/parser.spec.ts index d707ae0..b08cf6e 100644 --- a/src/parser.spec.ts +++ b/src/parser.spec.ts @@ -361,75 +361,75 @@ describe('parser', (): void => { ]); }); it('bad parameter parsing (no escaping, throw error)', (): void => { - expect(async () => parse('M(', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('M(', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 1: Cannot find closing ")" after last parameter', ); - expect(async () => parse('M(foo', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('M(foo', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 1: Cannot find closing ")" after last parameter', ); - expect(async () => parse('L(foo)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('L(foo)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing L() at index 1: Cannot find comma separating parameter 1 from the next one', ); - expect(async () => parse('L(foo,bar', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('L(foo,bar', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing L() at index 1: Cannot find closing ")" after last parameter', ); - expect(async () => parse('L(foo), bar', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('L(foo), bar', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing L() at index 1: Cannot find closing ")" after last parameter', ); - expect(async () => parse('P(', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('P(', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing P() at index 1: Cannot find closing ")" after last parameter', ); - expect(async () => parse('P(foo', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('P(foo', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing P() at index 1: Cannot find closing ")" after last parameter', ); }); it('bad module ref (throw error)', (): void => { - expect(async () => parse('M(foo)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('M(foo)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 1: Module name "foo" is not a FQCN', ); - expect(async () => parse(' M(foo.bar)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse(' M(foo.bar)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 2: Module name "foo.bar" is not a FQCN', ); - expect(async () => parse(' M(foo. bar.baz)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse(' M(foo. bar.baz)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 3: Module name "foo. bar.baz" is not a FQCN', ); - expect(async () => parse(' M(foo)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse(' M(foo)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing M() at index 4: Module name "foo" is not a FQCN', ); }); it('bad plugin ref (throw error)', (): void => { - expect(async () => parse('P(foo)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('P(foo)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing P() at index 1: Parameter "foo" is not of the form FQCN#type', ); - expect(async () => parse('P(f o.b r.b z#bar)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('P(f o.b r.b z#bar)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing P() at index 1: Plugin name "f o.b r.b z" is not a FQCN', ); - expect(async () => parse('P(foo.bar.baz#b m)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('P(foo.bar.baz#b m)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing P() at index 1: Plugin type "b m" is not valid', ); }); it('bad option name/return value (throw error)', (): void => { - expect(async () => parse('O(f o.b r.b z#bam:foobar)', { errors: 'exception' })).rejects.toThrow( - 'While parsing O() at index 1: Plugin name "f o.b r.b z" is not a FQCN', - ); - expect(async () => parse('O(foo.bar.baz#b m:foobar)', { errors: 'exception' })).rejects.toThrow( - 'While parsing O() at index 1: Plugin type "b m" is not valid', - ); - expect(async () => parse('O(foo:bar:baz)', { errors: 'exception' })).rejects.toThrow( + expect(async () => + parse('O(f o.b r.b z#bam:foobar)', { errors: 'exception', helpfulErrors: false }), + ).rejects.toThrow('While parsing O() at index 1: Plugin name "f o.b r.b z" is not a FQCN'); + expect(async () => + parse('O(foo.bar.baz#b m:foobar)', { errors: 'exception', helpfulErrors: false }), + ).rejects.toThrow('While parsing O() at index 1: Plugin type "b m" is not valid'); + expect(async () => parse('O(foo:bar:baz)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing O() at index 1: Invalid option/return value name "foo:bar:baz"', ); - expect(async () => parse('O(foo.bar.baz#role:bam)', { errors: 'exception' })).rejects.toThrow( + expect(async () => parse('O(foo.bar.baz#role:bam)', { errors: 'exception', helpfulErrors: false })).rejects.toThrow( 'While parsing O() at index 1: Role reference is missing entrypoint', ); }); it('bad parameter parsing (no escaping, error message)', (): void => { - expect(parse('M(')).toEqual([ + expect(parse('M(', { helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing M() at index 1: Cannot find closing ")" after last parameter' }], ]); - expect(parse('M(foo', { errors: 'message' })).toEqual([ + expect(parse('M(foo', { errors: 'message', helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing M() at index 1: Cannot find closing ")" after last parameter' }], ]); - expect(parse('L(foo)', { errors: 'message' })).toEqual([ + expect(parse('L(foo)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -437,30 +437,30 @@ describe('parser', (): void => { }, ], ]); - expect(parse('L(foo,bar', { errors: 'message' })).toEqual([ + expect(parse('L(foo,bar', { errors: 'message', helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing L() at index 1: Cannot find closing ")" after last parameter' }], ]); - expect(parse('L(foo), bar', { errors: 'message' })).toEqual([ + expect(parse('L(foo), bar', { errors: 'message', helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing L() at index 1: Cannot find closing ")" after last parameter' }], ]); - expect(parse('P(')).toEqual([ + expect(parse('P(', { helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing P() at index 1: Cannot find closing ")" after last parameter' }], ]); - expect(parse('P(foo', { errors: 'message' })).toEqual([ + expect(parse('P(foo', { errors: 'message', helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing P() at index 1: Cannot find closing ")" after last parameter' }], ]); }); it('bad module ref (error message)', (): void => { - expect(parse('M(foo)')).toEqual([ + expect(parse('M(foo)', { helpfulErrors: false })).toEqual([ [{ type: PartType.ERROR, message: 'While parsing M() at index 1: Module name "foo" is not a FQCN' }], ]); - expect(parse(' M(foo.bar)', { errors: 'message' })).toEqual([ + expect(parse(' M(foo.bar)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.TEXT, text: ' ' }, { type: PartType.ERROR, message: 'While parsing M() at index 2: Module name "foo.bar" is not a FQCN' }, ], ]); - expect(parse(' M(foo. bar.baz)', { errors: 'message' })).toEqual([ + expect(parse(' M(foo. bar.baz)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.TEXT, text: ' ' }, { @@ -469,7 +469,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse(' M(foo) baz', { errors: 'message' })).toEqual([ + expect(parse(' M(foo) baz', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.TEXT, text: ' ' }, { type: PartType.ERROR, message: 'While parsing M() at index 4: Module name "foo" is not a FQCN' }, @@ -478,7 +478,7 @@ describe('parser', (): void => { ]); }); it('bad plugin ref (error message)', (): void => { - expect(parse('P(foo)')).toEqual([ + expect(parse('P(foo)', { helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -486,7 +486,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse('P(f o.b r.b z#bar)', { errors: 'message' })).toEqual([ + expect(parse('P(f o.b r.b z#bar)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -494,7 +494,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse('P(foo.bar.baz#b m)', { errors: 'message' })).toEqual([ + expect(parse('P(foo.bar.baz#b m)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -504,7 +504,7 @@ describe('parser', (): void => { ]); }); it('bad option name/return value (error message)', (): void => { - expect(parse('O(f o.b r.b z#bam:foobar)')).toEqual([ + expect(parse('O(f o.b r.b z#bam:foobar)', { helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -512,7 +512,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse('O(foo.bar.baz#b m:foobar)', { errors: 'message' })).toEqual([ + expect(parse('O(foo.bar.baz#b m:foobar)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -520,7 +520,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse('O(foo:bar:baz)', { errors: 'message' })).toEqual([ + expect(parse('O(foo:bar:baz)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -528,7 +528,7 @@ describe('parser', (): void => { }, ], ]); - expect(parse('O(foo.bar.baz#role:bam)', { errors: 'message' })).toEqual([ + expect(parse('O(foo.bar.baz#role:bam)', { errors: 'message', helpfulErrors: false })).toEqual([ [ { type: PartType.ERROR, @@ -603,10 +603,14 @@ describe('parser engine', (): void => { ); }); it('combine wrong regexp with command map', (): void => { - expect(parseString('A B()', commandsReA, commandsMapA, {}, '')).toEqual([ + expect(parseString('A B()', commandsReA, commandsMapA, { helpfulErrors: false }, '')).toEqual([ { message: 'While parsing A at index 1: boo!', type: PartType.ERROR }, { text: ' B()', type: PartType.TEXT }, ]); + expect(parseString('A B()', commandsReA, commandsMapA, {}, '')).toEqual([ + { message: 'While parsing "A" at index 1: boo!', type: PartType.ERROR }, + { text: ' B()', type: PartType.TEXT }, + ]); }); it('combine wrong regexp with command map', (): void => { expect(parseString('A B(a, b, c)', commandsReB, commandsMapB, {}, '')).toEqual([ diff --git a/src/parser.ts b/src/parser.ts index 65517d0..54e2611 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -316,7 +316,11 @@ export function parseString( } } if (error !== undefined) { - error = `While parsing ${cmd}${command.parameters > 0 ? '()' : ''} at index ${match.index + 1}${where}: ${error}`; + const errorSource = + opts.helpfulErrors ?? true + ? `"${input.slice(index, endIndex)}"` + : `${cmd}${command.parameters > 0 ? '()' : ''}`; + error = `While parsing ${errorSource} at index ${match.index + 1}${where}: ${error}`; switch (opts.errors || 'message') { case 'ignore': break; diff --git a/test-vectors.yaml b/test-vectors.yaml index f16ccd6..14c18b3 100644 --- a/test-vectors.yaml +++ b/test-vectors.yaml @@ -1203,11 +1203,13 @@ test_vectors: `foo=' `bar.baz[123].bam[len(x) - 1]=' `foo=bar' `bar.baz[123].bam[len(x) - 1]=bar' - errors: + unhelpful_errors: source: - P(foo) - C(foo - R(foo,bar + parse_opts: + helpfulErrors: false html: |-
ERROR while parsing: While parsing P() at index 1 of paragraph 1: Parameter "foo" is not of the form FQCN#type
ERROR while parsing: While parsing C() at index 1 of paragraph 2: Cannot find closing ")" after last parameter
ERROR while parsing: While parsing R() at index 1 of paragraph 3: Cannot find closing ")" after last parameter
html_plain: |- @@ -1230,3 +1232,30 @@ test_vectors: [[ERROR while parsing: While parsing C() at index 1 of paragraph 2: Cannot find closing ")" after last parameter]] [[ERROR while parsing: While parsing R() at index 1 of paragraph 3: Cannot find closing ")" after last parameter]] + helpful_errors: + source: + - P(foo) + - C(foo + - R(foo,bar + html: |- +ERROR while parsing: While parsing "P(foo)" at index 1 of paragraph 1: Parameter "foo" is not of the form FQCN#type
ERROR while parsing: While parsing "C(foo" at index 1 of paragraph 2: Cannot find closing ")" after last parameter
ERROR while parsing: While parsing "R(foo,bar" at index 1 of paragraph 3: Cannot find closing ")" after last parameter
+ html_plain: |- +ERROR while parsing: While parsing "P(foo)" at index 1 of paragraph 1: Parameter "foo" is not of the form FQCN#type
ERROR while parsing: While parsing "C(foo" at index 1 of paragraph 2: Cannot find closing ")" after last parameter
ERROR while parsing: While parsing "R(foo,bar" at index 1 of paragraph 3: Cannot find closing ")" after last parameter
+ md: |- + ERROR while parsing: While parsing \"P\(foo\)\" at index 1 of paragraph 1\: Parameter \"foo\" is not of the form FQCN\#type + + ERROR while parsing: While parsing \"C\(foo\" at index 1 of paragraph 2\: Cannot find closing \"\)\" after last parameter + + ERROR while parsing: While parsing \"R\(foo\,bar\" at index 1 of paragraph 3\: Cannot find closing \"\)\" after last parameter + rst: |- + \ :strong:`ERROR while parsing`\ : While parsing "P(foo)" at index 1 of paragraph 1: Parameter "foo" is not of the form FQCN#type\ + + \ :strong:`ERROR while parsing`\ : While parsing "C(foo" at index 1 of paragraph 2: Cannot find closing ")" after last parameter\ + + \ :strong:`ERROR while parsing`\ : While parsing "R(foo,bar" at index 1 of paragraph 3: Cannot find closing ")" after last parameter\ + ansible_doc_text: |- + [[ERROR while parsing: While parsing "P(foo)" at index 1 of paragraph 1: Parameter "foo" is not of the form FQCN#type]] + + [[ERROR while parsing: While parsing "C(foo" at index 1 of paragraph 2: Cannot find closing ")" after last parameter]] + + [[ERROR while parsing: While parsing "R(foo,bar" at index 1 of paragraph 3: Cannot find closing ")" after last parameter]]