Skip to content

Commit

Permalink
Merge branch 'main' into async-lists
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR authored Sep 12, 2024
2 parents c592f67 + e546aac commit 7a6fdc0
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 123 deletions.
177 changes: 177 additions & 0 deletions .github/algorithm-format-check.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
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*)$/);
const grammarMatches =
filename === "Section 2 -- Language.md" &&
line.match(/^([A-Za-z0-9]+) :\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.dir(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.dir(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.dir(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.dir(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.dir(step);
console.log();
process.exitCode = 1;
}
}
} else if (grammarMatches) {
// This is super loosey-goosey
const [, grammarName, rest] = grammarMatches;
if (rest.trim() === "one of") {
// Still grammar, not algorithm
continue;
}
if (rest.trim() === "" && lines[i + 1] !== "") {
console.log(
`No empty space after grammar ${grammarName} header in '${filename}'`
);
console.log();
process.exitCode = 1;
}
if (!lines[i + 2].startsWith("- ")) {
// Not an algorithm; probably more grammar
continue;
}
for (let j = i + 2; j < l; j++) {
const step = lines[j];
if (!step.match(/^\s*(-|[0-9]+\.) /)) {
if (step !== "") {
console.log(`Bad grammar ${grammarName} step in '${filename}':`);
console.dir(step);
console.log();
process.exitCode = 1;
}
break;
}
if (!step.match(/[.:]$/)) {
console.log(
`Bad formatting for '${grammarName}' step (does not end in '.' or ':') in '${filename}':`
);
console.dir(step);
console.log();
process.exitCode = 1;
}
if (step.match(/^\s*(-|[0-9]\.)\s+[a-z]/)) {
console.log(
`Bad formatting of '${grammarName}' step (should start with a capital) in '${filename}':`
);
console.dir(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 '${grammarName}' step (true/false/null should be wrapped in curly braces, e.g. '{true}') in '${filename}':`
);
console.dir(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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![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)

# GraphQL

<img alt="GraphQL Logo" align="right" src="https://graphql.org/img/logo.svg" width="15%" />
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}.
```
4 changes: 4 additions & 0 deletions cspell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ words:
- tatooine
- zuck
- zuckerberg
# Forbid Alternative spellings
flagWords:
- implementor
- implementors
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
46 changes: 25 additions & 21 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ There are three types of operations that GraphQL models:
- subscription - a long-lived request that fetches data in response to source
events.

Each operation is represented by an optional operation name and a selection set.
Each operation is represented by an optional operation name and a _selection
set_.

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

:: A _selection set_ defines an ordered set of selections (fields, fragment
spreads and inline fragments) against an object, union or interface type.

```graphql example
{
id
Expand All @@ -346,14 +350,14 @@ data.
```

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

## Fields

Field : Alias? Name Arguments? Directives? SelectionSet?

A selection set is primarily composed of fields. A field describes one discrete
piece of information available to request within a selection set.
A _selection set_ is primarily composed of fields. A field describes one
discrete piece of information available to request within a selection set.

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

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

InlineFragment : ... TypeCondition? Directives? SelectionSet

Fragments can also be defined inline within a selection set. This is useful for
conditionally including fields based on a type condition or applying a directive
to a selection set.
Fragments can also be defined inline within a _selection set_. This is useful
for conditionally including fields based on a type condition or applying a
directive to a selection set.

This feature of standard fragment inclusion was demonstrated in the
`query FragmentTyping` example above. We could accomplish the same thing using
Expand Down Expand Up @@ -1032,7 +1036,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 @@ -1117,10 +1121,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 @@ -1164,11 +1168,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 @@ -1247,22 +1251,22 @@ input type.

Type : 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}
- 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 : [ 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

0 comments on commit 7a6fdc0

Please sign in to comment.