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

[WIP] Nonnull operator #5

Closed
wants to merge 50 commits into from
Closed
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
3519feb
Flow: use semicolon as separate inside types (#3089)
IvanGoncharov May 14, 2021
2fa47ec
TEMPORARY: move `*.d.ts` file in new directory
IvanGoncharov May 18, 2021
39825ee
Migrate to TS: rename `.js` to `.ts` and fix everything in latter PRs…
saihaj May 14, 2021
eb32d75
Switch to TS syntax (#3090)
saihaj May 15, 2021
524f244
match `.d.ts` definition
saihaj Feb 17, 2021
b4ea454
TS Migration: enable tests and remove flow infra (#3091)
saihaj May 15, 2021
fb24c86
TEMPORARY Workflow (#3093)
saihaj May 15, 2021
66b499b
TEMPORARY: Replace `void` with `undefined`
saihaj Feb 16, 2021
2e89d04
build: rename to TS eslint rules and temporarily disable eslint rules
saihaj Feb 16, 2021
66d2623
build: relax tsconfig
saihaj Oct 27, 2020
9341317
Replace `$FlowFixMe` with `@ts-expect-error`
saihaj May 16, 2021
cc9b99f
refactor: convert ObjMap to use `Record`
saihaj May 16, 2021
cbe849b
feat: typecast to ensure type safety
saihaj Feb 17, 2021
6a9edcd
convert `$FlowExpectedError` to `@ts-expect-error`
saihaj May 16, 2021
1fd3dc6
remove unused `@ts-expect-error`
saihaj May 16, 2021
c7de521
add more `@ts-expect-error`
saihaj May 16, 2021
a5895dc
feat: Mutable type utility
saihaj Apr 12, 2021
0d010e8
add fixme and type assertions
saihaj May 16, 2021
050e5df
Start importing Wei's changes
May 18, 2021
0e96f91
moved all of wei's changes over
twof May 18, 2021
12505d1
getting required to field nodes
twof May 18, 2021
141d15f
add basic tests
magicmark May 19, 2021
7a341f3
tidy up
magicmark May 19, 2021
0ad30eb
Start adding validation logic
aprilrd May 19, 2021
21c745c
Added required field to FieldNode
xuewei8910 May 20, 2021
ab65f29
Change required to be a ternary for a possible ? operator
aprilrd May 21, 2021
bd88851
Add more validation test cases around nullability
aprilrd May 21, 2021
336d062
Added question mark token and corresponding behavior
xuewei8910 May 21, 2021
69db41d
Added tests for question mark
xuewei8910 May 21, 2021
f9f7958
Updated validation rule
xuewei8910 May 21, 2021
6b85f17
Fixed tests
xuewei8910 May 21, 2021
29d87b5
Updated the rule to leverage the existing type conflict check
xuewei8910 May 21, 2021
6532469
Fixed all the tests and clean up unused code
xuewei8910 May 21, 2021
788b9db
Fixed naming and comments
xuewei8910 May 21, 2021
e5ef653
Export utility method for third party codegen
xuewei8910 May 21, 2021
21cb73b
remove old dts
twof May 29, 2021
9318f00
fix ast.ts
twof May 29, 2021
4bac274
revert some dotfiles
twof May 29, 2021
e85ccf2
rebase cleanup
twof May 29, 2021
fb03ce7
cleanup
twof May 29, 2021
5dd60af
finish cleanup
twof May 29, 2021
2e92dc2
tests passing
twof May 29, 2021
4715816
new test and cleanup
twof Jun 8, 2021
8ef3fa8
add executor tests
twof Jun 9, 2021
2719232
address linter errors
twof Jun 9, 2021
187f655
address linter errors
twof Jun 9, 2021
20fdff6
satisfy linter errors
twof Jun 9, 2021
b522e2f
prettier run
twof Jun 9, 2021
e92f5f4
spelling fixes
twof Jun 9, 2021
d820029
more spelling
twof Jun 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"args": [
"src/validation/__tests__/OverlappingFieldsCanBeMergedRule-test.ts"
],
"internalConsoleOptions": "openOnSessionStart",
"skipFiles": ["<node_internals>/**"]
}
]
}
4 changes: 4 additions & 0 deletions src/__testUtils__/kitchenSinkQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
...frag @onFragmentSpread
}
}
field3!
requiredField4: field4!
field5?
optionalField6: field6?
}
... @skip(unless: $foo) {
id
Expand Down
6 changes: 5 additions & 1 deletion src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
GraphQLResolveInfo,
GraphQLTypeResolver,
GraphQLList,
getNullableType,
} from '../type/definition';
import { assertValidSchema } from '../type/validate';
import {
Expand All @@ -51,6 +52,7 @@ import {
GraphQLSkipDirective,
} from '../type/directives';
import {
GraphQLNonNull,
isObjectType,
isAbstractType,
isLeafType,
Expand All @@ -66,6 +68,7 @@ import {
getArgumentValues,
getDirectiveValues,
} from './values';
import { modifiedOutputType } from '../utilities/applyRequiredStatus';

/**
* Terminology
Expand Down Expand Up @@ -600,7 +603,8 @@ function resolveField(
return;
}

const returnType = fieldDef.type;
const returnType = modifiedOutputType(fieldDef.type, fieldNodes[0].required)

const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver;

const info = buildResolveInfo(
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ export {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
RequiredStatus,
} from './language/index';

export type {
Expand Down Expand Up @@ -436,6 +437,7 @@ export {
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
modifiedOutputType,
} from './utilities/index';

export type {
Expand Down
22 changes: 11 additions & 11 deletions src/language/__tests__/lexer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,16 @@ describe('Lexer', () => {
it('errors respect whitespace', () => {
let caughtError;
try {
lexOne(['', '', ' ?', ''].join('\n'));
lexOne(['', '', ' ~', ''].join('\n'));
} catch (error) {
caughtError = error;
}
expect(String(caughtError)).to.equal(dedent`
Syntax Error: Cannot parse the unexpected character "?".
Syntax Error: Cannot parse the unexpected character "~".

GraphQL request:3:5
2 |
3 | ?
3 | ~
| ^
4 |
`);
Expand All @@ -180,18 +180,18 @@ describe('Lexer', () => {
it('updates line numbers in error for file context', () => {
let caughtError;
try {
const str = ['', '', ' ?', ''].join('\n');
const str = ['', '', ' ~', ''].join('\n');
const source = new Source(str, 'foo.js', { line: 11, column: 12 });
new Lexer(source).advance();
} catch (error) {
caughtError = error;
}
expect(String(caughtError)).to.equal(dedent`
Syntax Error: Cannot parse the unexpected character "?".
Syntax Error: Cannot parse the unexpected character "~".

foo.js:13:6
12 |
13 | ?
13 | ~
| ^
14 |
`);
Expand All @@ -200,16 +200,16 @@ describe('Lexer', () => {
it('updates column numbers in error for file context', () => {
let caughtError;
try {
const source = new Source('?', 'foo.js', { line: 1, column: 5 });
const source = new Source('~', 'foo.js', { line: 1, column: 5 });
new Lexer(source).advance();
} catch (error) {
caughtError = error;
}
expect(String(caughtError)).to.equal(dedent`
Syntax Error: Cannot parse the unexpected character "?".
Syntax Error: Cannot parse the unexpected character "~".

foo.js:1:5
1 | ?
1 | ~
| ^
`);
});
Expand Down Expand Up @@ -820,8 +820,8 @@ describe('Lexer', () => {
locations: [{ line: 1, column: 1 }],
});

expectSyntaxError('?').to.deep.equal({
message: 'Syntax Error: Cannot parse the unexpected character "?".',
expectSyntaxError('~').to.deep.equal({
message: 'Syntax Error: Cannot parse the unexpected character "~".',
locations: [{ line: 1, column: 1 }],
});

Expand Down
55 changes: 55 additions & 0 deletions src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,56 @@ describe('Parser', () => {
).to.not.throw();
});

it('parses required field', () => {
expect(() =>
parse(`
query {
requiredField!
}
`),
).to.not.throw();
});

it('parses optional field', () => {
expect(() =>
parse(`
query {
optionalField?
}
`),
).to.not.throw();
});

it('parses required with alias', () => {
expect(() =>
parse(`
query {
requiredField: field!
}
`),
).to.not.throw();
});

it('does not parse aliased field with bang on left of colon', () => {
expect(() =>
parse(`
query {
requiredField!: field
}
`),
).to.throw();
});

it('does not parse aliased field with bang on left and right of colon', () => {
expect(() =>
parse(`
query {
requiredField!: field!
}
`),
).to.throw();
});

it('creates ast', () => {
const result = parse(dedent`
{
Expand Down Expand Up @@ -256,6 +306,7 @@ describe('Parser', () => {
},
],
directives: [],
required: 'unset',
selectionSet: {
kind: Kind.SELECTION_SET,
loc: { start: 16, end: 38 },
Expand All @@ -271,6 +322,7 @@ describe('Parser', () => {
},
arguments: [],
directives: [],
required: 'unset',
selectionSet: undefined,
},
{
Expand All @@ -284,6 +336,7 @@ describe('Parser', () => {
},
arguments: [],
directives: [],
required: 'unset',
selectionSet: undefined,
},
],
Expand Down Expand Up @@ -331,6 +384,7 @@ describe('Parser', () => {
},
arguments: [],
directives: [],
required: 'unset',
selectionSet: {
kind: Kind.SELECTION_SET,
loc: { start: 15, end: 27 },
Expand All @@ -346,6 +400,7 @@ describe('Parser', () => {
},
arguments: [],
directives: [],
required: 'unset',
selectionSet: undefined,
},
],
Expand Down
5 changes: 5 additions & 0 deletions src/language/__tests__/printer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { describe, it } from 'mocha';

import { dedent, dedentString } from '../../__testUtils__/dedent';
import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery';
import type { FieldNode } from '../ast';

import { parse } from '../parser';
import { print } from '../printer';
Expand Down Expand Up @@ -164,6 +165,10 @@ describe('Printer: Query document', () => {
...frag @onFragmentSpread
}
}
field3!
requiredField4: field4!
field5?
optionalField6: field6?
}
... @skip(unless: $foo) {
id
Expand Down
20 changes: 20 additions & 0 deletions src/language/__tests__/visitor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,26 @@ describe('Visitor', () => {
['leave', 'Field', 1, undefined],
['leave', 'SelectionSet', 'selectionSet', 'Field'],
['leave', 'Field', 0, undefined],
['enter', 'Field', 1, undefined],
['enter', 'Name', 'name', 'Field'],
['leave', 'Name', 'name', 'Field'],
['leave', 'Field', 1, undefined],
['enter', 'Field', 2, undefined],
['enter', 'Name', 'alias', 'Field'],
['leave', 'Name', 'alias', 'Field'],
['enter', 'Name', 'name', 'Field'],
['leave', 'Name', 'name', 'Field'],
['leave', 'Field', 2, undefined],
['enter', 'Field', 3, undefined],
['enter', 'Name', 'name', 'Field'],
['leave', 'Name', 'name', 'Field'],
['leave', 'Field', 3, undefined],
['enter', 'Field', 4, undefined],
['enter', 'Name', 'alias', 'Field'],
['leave', 'Name', 'alias', 'Field'],
['enter', 'Name', 'name', 'Field'],
['leave', 'Name', 'name', 'Field'],
['leave', 'Field', 4, undefined],
['leave', 'SelectionSet', 'selectionSet', 'InlineFragment'],
['leave', 'InlineFragment', 1, undefined],
['enter', 'InlineFragment', 2, undefined],
Expand Down
3 changes: 3 additions & 0 deletions src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ export interface SelectionSetNode {

export type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode;

export type RequiredStatus = 'required' | 'optional' | 'unset';

export interface FieldNode {
readonly kind: 'Field';
readonly loc?: Location;
Expand All @@ -296,6 +298,7 @@ export interface FieldNode {
readonly arguments?: ReadonlyArray<ArgumentNode>;
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly selectionSet?: SelectionSetNode;
readonly required?: RequiredStatus;
}

export interface ArgumentNode {
Expand Down
1 change: 1 addition & 0 deletions src/language/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export type {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
RequiredStatus,
} from './ast';

export {
Expand Down
2 changes: 2 additions & 0 deletions src/language/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ function readToken(lexer: Lexer, prev: Token): Token {
case 56: // 8
case 57: // 9
return readNumber(source, pos, code, line, col, prev);
case 63: // ?
return new Token(TokenKind.QUESTION_MARK, pos, pos + 1, line, col, prev);
case 65: // A
case 66: // B
case 67: // C
Expand Down
13 changes: 12 additions & 1 deletion src/language/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export class Parser {
/**
* SelectionSet : { Selection+ }
*/
parseSelectionSet(): SelectionSetNode {
parseSelectionSet(): SelectionSetNode {
return this.node<SelectionSetNode>(this._lexer.token, {
kind: Kind.SELECTION_SET,
selections: this.many(
Expand Down Expand Up @@ -382,13 +382,23 @@ export class Parser {
const nameOrAlias = this.parseName();
let alias;
let name;

if (this.expectOptionalToken(TokenKind.COLON)) {
alias = nameOrAlias;
name = this.parseName();
} else {
name = nameOrAlias;
}

let required;
if (this.expectOptionalToken(TokenKind.BANG)) {
required = 'required'
} else if (this.expectOptionalToken(TokenKind.QUESTION_MARK)) {
required = 'optional'
} else {
required = 'unset'
}

return this.node<FieldNode>(start, {
kind: Kind.FIELD,
alias,
Expand All @@ -398,6 +408,7 @@ export class Parser {
selectionSet: this.peek(TokenKind.BRACE_L)
? this.parseSelectionSet()
: undefined,
required,
});
}

Expand Down
10 changes: 8 additions & 2 deletions src/language/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,14 @@ const printDocASTReducer: ASTReducer<string> = {
SelectionSet: { leave: ({ selections }) => block(selections) },

Field: {
leave({ alias, name, arguments: args, directives, selectionSet }) {
const prefix = wrap('', alias, ': ') + name;
leave({ alias, name, arguments: args, directives, selectionSet, required }) {
let prefix = wrap('', alias, ': ') + name;
if (required === 'required') {
prefix += '!'
} else if (required === 'optional') {
prefix += '?'
}

let argsLine = prefix + wrap('(', join(args, ', '), ')');

if (argsLine.length > MAX_LINE_LENGTH) {
Expand Down
1 change: 1 addition & 0 deletions src/language/tokenKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const TokenKind = Object.freeze({
STRING: 'String',
BLOCK_STRING: 'BlockString',
COMMENT: 'Comment',
QUESTION_MARK: '?',
} as const);

/**
Expand Down
Loading