Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print minified location #1985

Merged
merged 2 commits into from Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/error/__tests__/GraphQLError-test.js
Expand Up @@ -217,16 +217,16 @@ describe('printError', () => {
Example error with two nodes

SourceA:2:10
1: type Foo {
2: field: String
^
3: }
1 | type Foo {
2 | field: String
| ^
3 | }

SourceB:2:10
1: type Foo {
2: field: Int
^
3: }
1 | type Foo {
2 | field: Int
| ^
3 | }
`);
});
});
20 changes: 10 additions & 10 deletions src/language/__tests__/lexer-test.js
Expand Up @@ -122,10 +122,10 @@ describe('Lexer', () => {
Syntax Error: Cannot parse the unexpected character "?".

GraphQL request:3:5
2:
3: ?
^
4:
2 |
3 | ?
| ^
4 |
`);
});

Expand All @@ -142,10 +142,10 @@ describe('Lexer', () => {
Syntax Error: Cannot parse the unexpected character "?".

foo.js:13:6
12:
13: ?
^
14:
12 |
13 | ?
| ^
14 |
`);
});

Expand All @@ -161,8 +161,8 @@ describe('Lexer', () => {
Syntax Error: Cannot parse the unexpected character "?".

foo.js:1:5
1: ?
^
1 | ?
| ^
`);
});

Expand Down
8 changes: 4 additions & 4 deletions src/language/__tests__/parser-test.js
Expand Up @@ -48,8 +48,8 @@ describe('Parser', () => {
Syntax Error: Expected Name, found <EOF>

GraphQL request:1:2
1: {
^
1 | {
| ^
`);

expectSyntaxError(
Expand Down Expand Up @@ -85,8 +85,8 @@ describe('Parser', () => {
Syntax Error: Expected {, found <EOF>

MyQuery.graphql:1:6
1: query
^
1 | query
| ^
`);
});

Expand Down
53 changes: 47 additions & 6 deletions src/language/__tests__/printLocation-test.js
Expand Up @@ -7,7 +7,48 @@ import dedent from '../../jsutils/dedent';
import { Source } from '../../language';
import { printSourceLocation } from '../printLocation';

describe('printLocation', () => {
describe('printSourceLocation', () => {
it('prints minified documents', () => {
const minifiedSource = new Source(
'query SomeMiniFiedQueryWithErrorInside($foo:String!=FIRST_ERROR_HERE$bar:String){someField(foo:$foo bar:$bar baz:SECOND_ERROR_HERE){fieldA fieldB{fieldC fieldD...on THIRD_ERROR_HERE}}}',
);

const firstLocation = printSourceLocation(minifiedSource, {
line: 1,
column: minifiedSource.body.indexOf('FIRST_ERROR_HERE') + 1,
});
expect(firstLocation + '\n').to.equal(dedent`
GraphQL request:1:53
1 | query SomeMiniFiedQueryWithErrorInside($foo:String!=FIRST_ERROR_HERE$bar:String)
| ^
| {someField(foo:$foo bar:$bar baz:SECOND_ERROR_HERE){fieldA fieldB{fieldC fieldD.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Queries, particularly when compressed, can have a long line length. In narrow viewports (small Terminal windows, etc.) this results in the query wrapping over multiple lines.

@jaydenseric I think, I finally figured out a generic solution to the problem that you described in #1786. This test (+ 2 test cases below) shows the new format handles error location in the beginning, in the middle and in the end of the minified document.
What do you think?

`);

const secondLocation = printSourceLocation(minifiedSource, {
line: 1,
column: minifiedSource.body.indexOf('SECOND_ERROR_HERE') + 1,
});
expect(secondLocation + '\n').to.equal(dedent`
GraphQL request:1:114
1 | query SomeMiniFiedQueryWithErrorInside($foo:String!=FIRST_ERROR_HERE$bar:String)
| {someField(foo:$foo bar:$bar baz:SECOND_ERROR_HERE){fieldA fieldB{fieldC fieldD.
| ^
| ..on THIRD_ERROR_HERE}}}
`);

const thirdLocation = printSourceLocation(minifiedSource, {
line: 1,
column: minifiedSource.body.indexOf('THIRD_ERROR_HERE') + 1,
});
expect(thirdLocation + '\n').to.equal(dedent`
GraphQL request:1:166
1 | query SomeMiniFiedQueryWithErrorInside($foo:String!=FIRST_ERROR_HERE$bar:String)
| {someField(foo:$foo bar:$bar baz:SECOND_ERROR_HERE){fieldA fieldB{fieldC fieldD.
| ..on THIRD_ERROR_HERE}}}
| ^
`);
});

it('prints single digit line number with no padding', () => {
const result = printSourceLocation(
new Source('*', 'Test', { line: 9, column: 1 }),
Expand All @@ -16,8 +57,8 @@ describe('printLocation', () => {

expect(result + '\n').to.equal(dedent`
Test:9:1
9: *
^
9 | *
| ^
`);
});

Expand All @@ -29,9 +70,9 @@ describe('printLocation', () => {

expect(result + '\n').to.equal(dedent`
Test:9:1
9: *
^
10:
9 | *
| ^
10 |
`);
});
});
39 changes: 29 additions & 10 deletions src/language/printLocation.js
Expand Up @@ -30,30 +30,49 @@ export function printSourceLocation(

const columnOffset = sourceLocation.line === 1 ? firstLineColumnOffset : 0;
const columnNum = sourceLocation.column + columnOffset;
const locationStr = `${source.name}:${lineNum}:${columnNum}\n`;

const lines = body.split(/\r\n|[\n\r]/g);
const locationLine = lines[lineIndex];

// Special case for minified documents
if (locationLine.length > 120) {
const sublineIndex = Math.floor(columnNum / 80);
const sublineColumnNum = columnNum % 80;
const sublines = [];
for (let i = 0; i < locationLine.length; i += 80) {
sublines.push(locationLine.slice(i, i + 80));
}

return (
locationStr +
printPrefixedLines([
[`${lineNum}`, sublines[0]],
...sublines.slice(1, sublineIndex + 1).map(subline => ['', subline]),
[' ', whitespace(sublineColumnNum - 1) + '^'],
['', sublines[sublineIndex + 1]],
])
);
}

return (
`${source.name}:${lineNum}:${columnNum}\n` +
locationStr +
printPrefixedLines([
// Lines specified like this: ["prefix", "string"],
[`${lineNum - 1}: `, lines[lineIndex - 1]],
[`${lineNum}: `, lines[lineIndex]],
[`${lineNum - 1}`, lines[lineIndex - 1]],
[`${lineNum}`, locationLine],
['', whitespace(columnNum - 1) + '^'],
[`${lineNum + 1}: `, lines[lineIndex + 1]],
[`${lineNum + 1}`, lines[lineIndex + 1]],
])
);
}

function printPrefixedLines(lines: Array<[string, string]>): string {
const existingLines = lines.filter(([_, line]) => line !== undefined);

let padLen = 0;
for (const [prefix] of existingLines) {
padLen = Math.max(padLen, prefix.length);
}

const padLen = Math.max(...existingLines.map(([prefix]) => prefix.length));
return existingLines
.map(([prefix, line]) => lpad(padLen, prefix) + line)
.map(([prefix, line]) => lpad(padLen, prefix) + ' | ' + line)
.join('\n');
}

Expand Down
6 changes: 3 additions & 3 deletions src/utilities/__tests__/stripIgnoredCharacters-test.js
Expand Up @@ -157,9 +157,9 @@ describe('stripIgnoredCharacters', () => {
Syntax Error: Unterminated string.

GraphQL request:1:13
1: { foo(arg: "
^
2: "
1 | { foo(arg: "
| ^
2 | "
`);
});

Expand Down