Skip to content

Commit 7a6fdc0

Browse files
authored
Merge branch 'main' into async-lists
2 parents c592f67 + e546aac commit 7a6fdc0

12 files changed

+349
-123
lines changed

.github/algorithm-format-check.mjs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { readFile, readdir } from "node:fs/promises";
2+
3+
const SPEC_DIR = new URL("../spec", import.meta.url).pathname;
4+
5+
process.exitCode = 0;
6+
const filenames = await readdir(SPEC_DIR);
7+
for (const filename of filenames) {
8+
if (!filename.endsWith(".md")) {
9+
continue;
10+
}
11+
const markdown = await readFile(`${SPEC_DIR}/${filename}`, "utf8");
12+
13+
/**
14+
* Not strictly 'lines' since we try and group indented things together as if
15+
* they were one line. Close enough though.
16+
*/
17+
const lines = markdown.split(/\n(?=[\S\n]|\s*(?:-|[0-9]+\.) )/);
18+
19+
for (let i = 0, l = lines.length; i < l; i++) {
20+
const line = lines[i];
21+
22+
// Check algorithm is consistently formatted
23+
{
24+
// Is it an algorithm definition?
25+
const matches = line.match(/^([a-z0-9A-Z]+)(\s*)\(([^)]*)\)(\s*):(\s*)$/);
26+
const grammarMatches =
27+
filename === "Section 2 -- Language.md" &&
28+
line.match(/^([A-Za-z0-9]+) :\s+((\S).*)$/);
29+
if (matches) {
30+
const [, algorithmName, ns1, _args, ns2, ns3] = matches;
31+
if (ns1 || ns2 || ns3) {
32+
console.log(
33+
`Bad whitespace in definition of ${algorithmName} in '${filename}':`
34+
);
35+
console.dir(line);
36+
console.log();
37+
process.exitCode = 1;
38+
}
39+
if (lines[i + 1] !== "") {
40+
console.log(
41+
`No empty space after algorithm ${algorithmName} header in '${filename}'`
42+
);
43+
console.log();
44+
process.exitCode = 1;
45+
}
46+
for (let j = i + 2; j < l; j++) {
47+
const step = lines[j];
48+
if (!step.match(/^\s*(-|[0-9]+\.) /)) {
49+
if (step !== "") {
50+
console.log(
51+
`Bad algorithm ${algorithmName} step in '${filename}':`
52+
);
53+
console.dir(step);
54+
console.log();
55+
process.exitCode = 1;
56+
}
57+
break;
58+
}
59+
if (!step.match(/[.:]$/)) {
60+
console.log(
61+
`Bad formatting for '${algorithmName}' step (does not end in '.' or ':') in '${filename}':`
62+
);
63+
console.dir(step);
64+
console.log();
65+
process.exitCode = 1;
66+
}
67+
if (step.match(/^\s*(-|[0-9]\.)\s+[a-z]/)) {
68+
console.log(
69+
`Bad formatting of '${algorithmName}' step (should start with a capital) in '${filename}':`
70+
);
71+
console.dir(step);
72+
console.log();
73+
process.exitCode = 1;
74+
}
75+
const trimmedInnerLine = step.replace(/\s+/g, " ");
76+
if (
77+
trimmedInnerLine.match(
78+
/(?:[rR]eturn|is (?:not )?)(true|false|null)\b/
79+
) &&
80+
!trimmedInnerLine.match(/null or empty/)
81+
) {
82+
console.log(
83+
`Potential bad formatting of '${algorithmName}' step (true/false/null should be wrapped in curly braces, e.g. '{true}') in '${filename}':`
84+
);
85+
console.dir(step);
86+
console.log();
87+
process.exitCode = 1;
88+
}
89+
}
90+
} else if (grammarMatches) {
91+
// This is super loosey-goosey
92+
const [, grammarName, rest] = grammarMatches;
93+
if (rest.trim() === "one of") {
94+
// Still grammar, not algorithm
95+
continue;
96+
}
97+
if (rest.trim() === "" && lines[i + 1] !== "") {
98+
console.log(
99+
`No empty space after grammar ${grammarName} header in '${filename}'`
100+
);
101+
console.log();
102+
process.exitCode = 1;
103+
}
104+
if (!lines[i + 2].startsWith("- ")) {
105+
// Not an algorithm; probably more grammar
106+
continue;
107+
}
108+
for (let j = i + 2; j < l; j++) {
109+
const step = lines[j];
110+
if (!step.match(/^\s*(-|[0-9]+\.) /)) {
111+
if (step !== "") {
112+
console.log(`Bad grammar ${grammarName} step in '${filename}':`);
113+
console.dir(step);
114+
console.log();
115+
process.exitCode = 1;
116+
}
117+
break;
118+
}
119+
if (!step.match(/[.:]$/)) {
120+
console.log(
121+
`Bad formatting for '${grammarName}' step (does not end in '.' or ':') in '${filename}':`
122+
);
123+
console.dir(step);
124+
console.log();
125+
process.exitCode = 1;
126+
}
127+
if (step.match(/^\s*(-|[0-9]\.)\s+[a-z]/)) {
128+
console.log(
129+
`Bad formatting of '${grammarName}' step (should start with a capital) in '${filename}':`
130+
);
131+
console.dir(step);
132+
console.log();
133+
process.exitCode = 1;
134+
}
135+
const trimmedInnerLine = step.replace(/\s+/g, " ");
136+
if (
137+
trimmedInnerLine.match(
138+
/(?:[rR]eturn|is (?:not )?)(true|false|null)\b/
139+
) &&
140+
!trimmedInnerLine.match(/null or empty/)
141+
) {
142+
console.log(
143+
`Potential bad formatting of '${grammarName}' step (true/false/null should be wrapped in curly braces, e.g. '{true}') in '${filename}':`
144+
);
145+
console.dir(step);
146+
console.log();
147+
process.exitCode = 1;
148+
}
149+
}
150+
}
151+
}
152+
153+
// Check `- ...:` step is followed by an indent
154+
{
155+
const matches = line.match(/^(\s*)- .*:\s*$/);
156+
if (matches) {
157+
const indent = matches[1];
158+
const nextLine = lines[i + 1];
159+
if (!nextLine.startsWith(`${indent} `)) {
160+
console.log(
161+
`Lacking indent in '${filename}' following ':' character:`
162+
);
163+
console.dir(line);
164+
console.dir(nextLine);
165+
console.log();
166+
// TODO: process.exitCode = 1;
167+
}
168+
}
169+
}
170+
}
171+
}
172+
173+
if (process.exitCode === 0) {
174+
console.log(`Everything looks okay!`);
175+
} else {
176+
console.log(`Please resolve the errors detailed above.`);
177+
}

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- uses: actions/setup-node@v3
2222
- run: npm ci
2323
- run: npm run test:format
24+
- run: npm run test:algorithm-format
2425
test-build:
2526
runs-on: ubuntu-latest
2627
steps:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[![GraphQLConf 2024 Banner: September 10-12, San Francisco. Hosted by the GraphQL Foundation](https://github.com/user-attachments/assets/0203f10b-ae1e-4fe1-9222-6547fa2bbd5d)](https://graphql.org/conf/2024/?utm_source=github&utm_medium=graphql_spec&utm_campaign=readme)
2+
13
# GraphQL
24

35
<img alt="GraphQL Logo" align="right" src="https://graphql.org/img/logo.svg" width="15%" />

STYLE_GUIDE.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,30 @@ hyphens) should be capitalized, with the following exceptions:
5555
All elements in hyphenated words follow the same rules, e.g. headings may
5656
contain `Non-Null`, `Context-Free`, `Built-in` (`in` is a preposition, so is not
5757
capitalized).
58+
59+
## Algorithms
60+
61+
A named algorithm definition starts with the name of the algorithm in
62+
`PascalCase`, an open parenthesis, a comma-and-space separated list of
63+
arguments, a close parenthesis and then a colon. It is followed by a blank
64+
newline and a list of steps in the algorithm which may be numbered or bulleted.
65+
66+
Each step in an algorithm should either end in a colon (`:`) with an indented
67+
step on the next line, or a fullstop (`.`). (A step after a step ending in a
68+
full stop may or may not be indented, use your discretion.)
69+
70+
Indentation in algorithms is significant.
71+
72+
Every step in an algorithm should start with a capital letter.
73+
74+
```
75+
MyAlgorithm(argOne, argTwo):
76+
77+
- Let {something} be {true}.
78+
- For each {arg} in {argOne}:
79+
- If {arg} is greater than {argTwo}:
80+
- Let {something} be {false}.
81+
- Otherwise if {arg} is less than {argTwo}:
82+
- Let {something} be {true}.
83+
- Return {something}.
84+
```

cspell.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ words:
2121
- tatooine
2222
- zuck
2323
- zuckerberg
24+
# Forbid Alternative spellings
25+
flagWords:
26+
- implementor
27+
- implementors

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"test:spelling": "cspell \"spec/**/*.md\" README.md",
1818
"format": "prettier --write \"**/*.{md,yml,yaml,json}\"",
1919
"test:format": "prettier --check \"**/*.{md,yml,yaml,json}\" || npm run suggest:format",
20+
"test:algorithm-format": "node .github/algorithm-format-check.mjs",
2021
"suggest:format": "echo \"\nTo resolve this, run: $(tput bold)npm run format$(tput sgr0)\" && exit 1",
2122
"build": "./build.sh",
2223
"test:build": "spec-md --metadata spec/metadata.json spec/GraphQL.md > /dev/null",

spec/Section 2 -- Language.md

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ There are three types of operations that GraphQL models:
291291
- subscription - a long-lived request that fetches data in response to source
292292
events.
293293

294-
Each operation is represented by an optional operation name and a selection set.
294+
Each operation is represented by an optional operation name and a _selection
295+
set_.
295296

296297
For example, this mutation operation might "like" a story and then retrieve the
297298
new number of likes:
@@ -337,6 +338,9 @@ An operation selects the set of information it needs, and will receive exactly
337338
that information and nothing more, avoiding over-fetching and under-fetching
338339
data.
339340

341+
:: A _selection set_ defines an ordered set of selections (fields, fragment
342+
spreads and inline fragments) against an object, union or interface type.
343+
340344
```graphql example
341345
{
342346
id
@@ -346,14 +350,14 @@ data.
346350
```
347351

348352
In this query operation, the `id`, `firstName`, and `lastName` fields form a
349-
selection set. Selection sets may also contain fragment references.
353+
_selection set_. Selection sets may also contain fragment references.
350354

351355
## Fields
352356

353357
Field : Alias? Name Arguments? Directives? SelectionSet?
354358

355-
A selection set is primarily composed of fields. A field describes one discrete
356-
piece of information available to request within a selection set.
359+
A _selection set_ is primarily composed of fields. A field describes one
360+
discrete piece of information available to request within a selection set.
357361

358362
Some fields describe complex data or relationships to other data. In order to
359363
further explore this data, a field may itself contain a selection set, allowing
@@ -381,7 +385,7 @@ down to scalar values.
381385
}
382386
```
383387

384-
Fields in the top-level selection set of an operation often represent some
388+
Fields in the top-level _selection set_ of an operation often represent some
385389
information that is globally accessible to your application and its current
386390
viewer. Some typical examples of these top fields include references to a
387391
current logged-in viewer, or accessing certain types of data referenced by a
@@ -667,9 +671,9 @@ be present and `likers` will not. Conversely when the result is a `Page`,
667671

668672
InlineFragment : ... TypeCondition? Directives? SelectionSet
669673

670-
Fragments can also be defined inline within a selection set. This is useful for
671-
conditionally including fields based on a type condition or applying a directive
672-
to a selection set.
674+
Fragments can also be defined inline within a _selection set_. This is useful
675+
for conditionally including fields based on a type condition or applying a
676+
directive to a selection set.
673677

674678
This feature of standard fragment inclusion was demonstrated in the
675679
`query FragmentTyping` example above. We could accomplish the same thing using
@@ -1032,7 +1036,7 @@ BlockStringValue(rawValue):
10321036
- Let {lines} be the result of splitting {rawValue} by {LineTerminator}.
10331037
- Let {commonIndent} be {null}.
10341038
- For each {line} in {lines}:
1035-
- If {line} is the first item in {lines}, continue to the next line.
1039+
- If {line} is the first item in {lines}, continue to the next {line}.
10361040
- Let {length} be the number of characters in {line}.
10371041
- Let {indent} be the number of leading consecutive {WhiteSpace} characters in
10381042
{line}.
@@ -1117,10 +1121,10 @@ ListValue : [ ]
11171121
ListValue : [ Value+ ]
11181122

11191123
- Let {inputList} be a new empty list value.
1120-
- For each {Value+}
1124+
- For each {Value+}:
11211125
- Let {value} be the result of evaluating {Value}.
11221126
- Append {value} to {inputList}.
1123-
- Return {inputList}
1127+
- Return {inputList}.
11241128

11251129
### Input Object Values
11261130

@@ -1164,11 +1168,11 @@ ObjectValue : { }
11641168
ObjectValue : { ObjectField+ }
11651169

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

11731177
## Variables
11741178

@@ -1247,22 +1251,22 @@ input type.
12471251

12481252
Type : Name
12491253

1250-
- Let {name} be the string value of {Name}
1251-
- Let {type} be the type defined in the Schema named {name}
1252-
- {type} must not be {null}
1253-
- Return {type}
1254+
- Let {name} be the string value of {Name}.
1255+
- Let {type} be the type defined in the Schema named {name}.
1256+
- {type} must not be {null}.
1257+
- Return {type}.
12541258

12551259
Type : [ Type ]
12561260

1257-
- Let {itemType} be the result of evaluating {Type}
1261+
- Let {itemType} be the result of evaluating {Type}.
12581262
- Let {type} be a List type where {itemType} is the contained type.
1259-
- Return {type}
1263+
- Return {type}.
12601264

12611265
Type : Type !
12621266

1263-
- Let {nullableType} be the result of evaluating {Type}
1267+
- Let {nullableType} be the result of evaluating {Type}.
12641268
- Let {type} be a Non-Null type where {nullableType} is the contained type.
1265-
- Return {type}
1269+
- Return {type}.
12661270

12671271
## Directives
12681272

0 commit comments

Comments
 (0)