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

Fragment args 2024 amendments #3

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
12b7ad7
add explanation about argument name uniqueness. (#891)
dugenkui03 Feb 2, 2023
4e93488
Fix ambiguity around when schema definition may be omitted (#987)
benjie Feb 9, 2023
afc0a35
Add links to contributed custom scalar specs at scalars.graphql.org (…
dondonz Feb 11, 2023
a5cc6ea
Clarify that selection sets cannot be empty (#1025)
benjie May 19, 2023
6b7c2c4
Remove "subscriptions is a significant change" sentence (#983)
rivantsov Jul 8, 2023
3adfcca
Add explicit definition for BlockString (#1042)
benjie Sep 8, 2023
56d6107
Fix heading level for Required Arguments validation rule (#1055)
goto-bus-stop Nov 10, 2023
feac5a5
Fix punctuation in some algorithms (#1067)
yaacovCR Dec 14, 2023
8682a86
Fix 'response error' -> 'request error' (#1016)
benjie Mar 7, 2024
0ba7cdf
Enforce consistent punctuation in algorithms (#1069)
benjie Mar 7, 2024
b850f4a
RFC: Fragment Arguments
mjmahone Jan 2, 2023
76011b0
address https://github.com/graphql/graphql-js/pull/3835#discussion_r1…
JoviDeCroock Feb 7, 2024
08d3d32
wip
JoviDeCroock Feb 7, 2024
5584176
wording
JoviDeCroock Feb 12, 2024
4dd3fe3
corrections
JoviDeCroock Feb 16, 2024
440aa9c
Update spec/Section 2 -- Language.md
JoviDeCroock Feb 27, 2024
48fa841
address validation comments
JoviDeCroock Mar 8, 2024
d7590fa
address language comments
JoviDeCroock Mar 8, 2024
78b5c56
Remove unused `$__UNSET`
JoviDeCroock Mar 26, 2024
7cad383
Apply Benjie's suggestions
JoviDeCroock Mar 27, 2024
03ba255
conciser validation
JoviDeCroock Mar 27, 2024
08927c6
Apply suggestions from code review
JoviDeCroock Mar 29, 2024
3bc4e59
Apply suggestions from code review
JoviDeCroock Mar 29, 2024
969f692
formatting and expand examples
JoviDeCroock Mar 30, 2024
2e0c5ad
add in undefined fragment
JoviDeCroock Mar 30, 2024
51b9ef7
unset instead of null
JoviDeCroock Mar 30, 2024
3230384
shorten
JoviDeCroock Mar 30, 2024
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
114 changes: 114 additions & 0 deletions .github/algorithm-format-check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { readFile, readdir } from "node:fs/promises";

const SPEC_DIR = new URL("../spec", import.meta.url).pathname;

process.exitCode = 0;
const filenames = await readdir(SPEC_DIR);
for (const filename of filenames) {
if (!filename.endsWith(".md")) {
continue;
}
const markdown = await readFile(`${SPEC_DIR}/${filename}`, "utf8");

/**
* Not strictly 'lines' since we try and group indented things together as if
* they were one line. Close enough though.
*/
const lines = markdown.split(/\n(?=[\S\n]|\s*(?:-|[0-9]+\.) )/);

for (let i = 0, l = lines.length; i < l; i++) {
const line = lines[i];

// Check algorithm is consistently formatted
{
// Is it an algorithm definition?
const matches = line.match(/^([a-z0-9A-Z]+)(\s*)\(([^)]*)\)(\s*):(\s*)$/);
if (matches) {
const [, algorithmName, ns1, _args, ns2, ns3] = matches;
if (ns1 || ns2 || ns3) {
console.log(
`Bad whitespace in definition of ${algorithmName} in '${filename}':`
);
console.log(line);
console.log();
process.exitCode = 1;
}
if (lines[i + 1] !== "") {
console.log(
`No empty space after algorithm ${algorithmName} header in '${filename}'`
);
console.log();
process.exitCode = 1;
}
for (let j = i + 2; j < l; j++) {
const step = lines[j];
if (!step.match(/^\s*(-|[0-9]+\.) /)) {
if (step !== "") {
console.log(
`Bad algorithm ${algorithmName} step in '${filename}':`
);
console.log(step);
console.log();
process.exitCode = 1;
}
break;
}
if (!step.match(/[.:]$/)) {
console.log(
`Bad formatting for '${algorithmName}' step (does not end in '.' or ':') in '${filename}':`
);
console.log(step);
console.log();
process.exitCode = 1;
}
if (step.match(/^\s*(-|[0-9]\.)\s+[a-z]/)) {
console.log(
`Bad formatting of '${algorithmName}' step (should start with a capital) in '${filename}':`
);
console.log(step);
console.log();
process.exitCode = 1;
}
const trimmedInnerLine = step.replace(/\s+/g, " ");
if (
trimmedInnerLine.match(
/(?:[rR]eturn|is (?:not )?)(true|false|null)\b/
) &&
!trimmedInnerLine.match(/null or empty/)
) {
console.log(
`Potential bad formatting of '${algorithmName}' step (true/false/null should be wrapped in curly braces, e.g. '{true}') in '${filename}':`
);
console.log(step);
console.log();
process.exitCode = 1;
}
}
}
}

// Check `- ...:` step is followed by an indent
{
const matches = line.match(/^(\s*)- .*:\s*$/);
if (matches) {
const indent = matches[1];
const nextLine = lines[i + 1];
if (!nextLine.startsWith(`${indent} `)) {
console.log(
`Lacking indent in '${filename}' following ':' character:`
);
console.dir(line);
console.dir(nextLine);
console.log();
// TODO: process.exitCode = 1;
}
}
}
}
}

if (process.exitCode === 0) {
console.log(`Everything looks okay!`);
} else {
console.log(`Please resolve the errors detailed above.`);
}
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run test:format
- run: npm run test:algorithm-format
test-build:
runs-on: ubuntu-latest
steps:
Expand Down
27 changes: 27 additions & 0 deletions STYLE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,30 @@ hyphens) should be capitalized, with the following exceptions:
All elements in hyphenated words follow the same rules, e.g. headings may
contain `Non-Null`, `Context-Free`, `Built-in` (`in` is a preposition, so is not
capitalized).

## Algorithms

A named algorithm definition starts with the name of the algorithm in
`PascalCase`, an open parenthesis, a comma-and-space separated list of
arguments, a close parenthesis and then a colon. It is followed by a blank
newline and a list of steps in the algorithm which may be numbered or bulleted.

Each step in an algorithm should either end in a colon (`:`) with an indented
step on the next line, or a fullstop (`.`). (A step after a step ending in a
full stop may or may not be indented, use your discretion.)

Indentation in algorithms is significant.

Every step in an algorithm should start with a capital letter.

```
MyAlgorithm(argOne, argTwo):

- Let {something} be {true}.
- For each {arg} in {argOne}:
- If {arg} is greater than {argTwo}:
- Let {something} be {false}.
- Otherwise if {arg} is less than {argTwo}:
- Let {something} be {true}.
- Return {something}.
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"test:spelling": "cspell \"spec/**/*.md\" README.md",
"format": "prettier --write \"**/*.{md,yml,yaml,json}\"",
"test:format": "prettier --check \"**/*.{md,yml,yaml,json}\" || npm run suggest:format",
"test:algorithm-format": "node .github/algorithm-format-check.mjs",
"suggest:format": "echo \"\nTo resolve this, run: $(tput bold)npm run format$(tput sgr0)\" && exit 1",
"build": "./build.sh",
"test:build": "spec-md --metadata spec/metadata.json spec/GraphQL.md > /dev/null",
Expand Down
10 changes: 6 additions & 4 deletions spec/Appendix B -- Grammar Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ StringValue ::

- `""` [lookahead != `"`]
- `"` StringCharacter+ `"`
- `"""` BlockStringCharacter\* `"""`
- BlockString

StringCharacter ::

Expand All @@ -121,6 +121,8 @@ HexDigit :: one of

EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t`

BlockString :: `"""` BlockStringCharacter\* `"""`

BlockStringCharacter ::

- SourceCharacter but not `"""` or `\"""`
Expand Down Expand Up @@ -168,12 +170,12 @@ Arguments[Const] : ( Argument[?Const]+ )

Argument[Const] : Name : Value[?Const]

FragmentSpread : ... FragmentName Directives?
FragmentSpread : ... FragmentName Arguments? Directives?

InlineFragment : ... TypeCondition? Directives? SelectionSet

FragmentDefinition : fragment FragmentName TypeCondition Directives?
SelectionSet
FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition
Directives? SelectionSet

FragmentName : Name but not `on`

Expand Down
109 changes: 87 additions & 22 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,10 +516,10 @@ which returns the result:

## Fragments

FragmentSpread : ... FragmentName Directives?
FragmentSpread : ... FragmentName Arguments? Directives?

FragmentDefinition : fragment FragmentName TypeCondition Directives?
SelectionSet
FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition
Directives? SelectionSet

FragmentName : Name but not `on`

Expand Down Expand Up @@ -806,7 +806,7 @@ StringValue ::

- `""` [lookahead != `"`]
- `"` StringCharacter+ `"`
- `"""` BlockStringCharacter\* `"""`
- BlockString

StringCharacter ::

Expand All @@ -827,6 +827,8 @@ HexDigit :: one of

EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t`

BlockString :: `"""` BlockStringCharacter\* `"""`

BlockStringCharacter ::

- SourceCharacter but not `"""` or `\"""`
Expand Down Expand Up @@ -1007,7 +1009,11 @@ StringCharacter :: `\` EscapedCharacter
| {`r`} | U+000D | carriage return |
| {`t`} | U+0009 | horizontal tab |

StringValue :: `"""` BlockStringCharacter\* `"""`
StringValue :: BlockString

- Return the _Unicode text_ by evaluating the {BlockString}.

BlockString :: `"""` BlockStringCharacter\* `"""`

- Let {rawValue} be the _Unicode text_ by concatenating the evaluation of all
{BlockStringCharacter} (which may be an empty sequence).
Expand All @@ -1026,7 +1032,7 @@ BlockStringValue(rawValue):
- Let {lines} be the result of splitting {rawValue} by {LineTerminator}.
- Let {commonIndent} be {null}.
- For each {line} in {lines}:
- If {line} is the first item in {lines}, continue to the next line.
- If {line} is the first item in {lines}, continue to the next {line}.
- Let {length} be the number of characters in {line}.
- Let {indent} be the number of leading consecutive {WhiteSpace} characters in
{line}.
Expand Down Expand Up @@ -1111,10 +1117,10 @@ ListValue : [ ]
ListValue : [ Value+ ]

- Let {inputList} be a new empty list value.
- For each {Value+}
- For each {Value+}:
- Let {value} be the result of evaluating {Value}.
- Append {value} to {inputList}.
- Return {inputList}
- Return {inputList}.

### Input Object Values

Expand Down Expand Up @@ -1158,11 +1164,11 @@ ObjectValue : { }
ObjectValue : { ObjectField+ }

- Let {inputObject} be a new input object value with no fields.
- For each {field} in {ObjectField+}
- For each {field} in {ObjectField+}:
- Let {name} be {Name} in {field}.
- Let {value} be the result of evaluating {Value} in {field}.
- Add a field to {inputObject} of name {name} containing value {value}.
- Return {inputObject}
- Return {inputObject}.

## Variables

Expand Down Expand Up @@ -1209,13 +1215,72 @@ size `60`:

**Variable Use Within Fragments**

Variables can be used within fragments. Variables have global scope with a given
operation, so a variable used within a fragment must be declared in any
top-level operation that transitively consumes that fragment. If a variable is
referenced in a fragment and is included by an operation that does not define
that variable, that operation is invalid (see
Variables can be used within fragments. Operation-defined variables have global
scope within a given operation. Fragment-defined variables have local scope
within the fragment definition in which they are defined. A variable used within
a fragment must either be declared in each top-level operation that transitively
consumes that fragment, or by that same fragment as a fragment variable
definition. If a variable referenced in a fragment is included by an operation
where neither the fragment nor the operation defines that variable, that
operation is invalid (see
[All Variable Uses Defined](#sec-All-Variable-Uses-Defined)).

## Fragment Variable Definitions

Fragments may define locally scoped variables. This allows fragments to be
reused while enabling the caller to specify the fragment's behavior.

For example, the profile picture may need to be a different size depending on
the parent context:

```graphql example
query userAndFriends {
user(id: 4) {
...dynamicProfilePic(size: 100)
friends(first: 10) {
id
name
...dynamicProfilePic
}
}
}

fragment dynamicProfilePic($size: Int! = 50) on User {
profilePic(size: $size)
}
```

In this case the `user` will have a larger `profilePic` than those found in the
list of `friends`.

A fragment-defined variable is scoped to the fragment that defines it.
Fragment-defined variables are allowed to shadow operation-defined variables.

```graphql example
query withShadowedVariables($size: Int!) {
user(id: 4) {
...variableProfilePic
}
secondUser: user(id: 5) {
...dynamicProfilePic(size: 10)
}
}

fragment variableProfilePic on User {
...dynamicProfilePic(size: $size)
}

fragment dynamicProfilePic($size: Int!) on User {
profilePic(size: $size)
}
```

The profilePic for `user` will be determined by the variables set by the
operation, while `secondUser` will always have a `profilePic` of size `10`. In
this case, the fragment `variableProfilePic` uses the operation-defined
variable, while `dynamicProfilePic` uses the value passed in via the fragment
spread's `size` argument.

## Type References

Type :
Expand All @@ -1241,22 +1306,22 @@ input type.

Type : Name

- Let {name} be the string value of {Name}
- Let {name} be the string value of {Name}.
- Let {type} be the type defined in the Schema named {name}
- {type} must not be {null}
- Return {type}
- {type} must not be {null}.
- Return {type}.

Type : [ Type ]

- Let {itemType} be the result of evaluating {Type}
- Let {itemType} be the result of evaluating {Type}.
- Let {type} be a List type where {itemType} is the contained type.
- Return {type}
- Return {type}.

Type : Type !

- Let {nullableType} be the result of evaluating {Type}
- Let {nullableType} be the result of evaluating {Type}.
- Let {type} be a Non-Null type where {nullableType} is the contained type.
- Return {type}
- Return {type}.

## Directives

Expand Down
Loading