diff --git a/.github/algorithm-format-check.mjs b/.github/algorithm-format-check.mjs index 13a31ece9..592d721de 100644 --- a/.github/algorithm-format-check.mjs +++ b/.github/algorithm-format-check.mjs @@ -23,13 +23,16 @@ for (const filename of filenames) { { // 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.log(line); + console.dir(line); console.log(); process.exitCode = 1; } @@ -47,7 +50,7 @@ for (const filename of filenames) { console.log( `Bad algorithm ${algorithmName} step in '${filename}':` ); - console.log(step); + console.dir(step); console.log(); process.exitCode = 1; } @@ -57,7 +60,7 @@ for (const filename of filenames) { console.log( `Bad formatting for '${algorithmName}' step (does not end in '.' or ':') in '${filename}':` ); - console.log(step); + console.dir(step); console.log(); process.exitCode = 1; } @@ -65,7 +68,7 @@ for (const filename of filenames) { console.log( `Bad formatting of '${algorithmName}' step (should start with a capital) in '${filename}':` ); - console.log(step); + console.dir(step); console.log(); process.exitCode = 1; } @@ -79,7 +82,67 @@ for (const filename of filenames) { 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.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; } diff --git a/README.md b/README.md index 458213632..09a7962a5 100644 --- a/README.md +++ b/README.md @@ -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 GraphQL Logo diff --git a/cspell.yml b/cspell.yml index e8aa73355..0ea1def96 100644 --- a/cspell.yml +++ b/cspell.yml @@ -21,3 +21,7 @@ words: - tatooine - zuck - zuckerberg +# Forbid Alternative spellings +flagWords: + - implementor + - implementors diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 44a7433b9..969d99f88 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -362,7 +362,7 @@ fragment aliasedLyingFieldTargetNotDefined on Dog { ``` For interfaces, direct field selection can only be done on fields. Fields of -concrete implementors are not relevant to the validity of the given +concrete implementers are not relevant to the validity of the given interface-typed selection set. For example, the following is valid: @@ -376,7 +376,7 @@ fragment interfaceFieldSelection on Pet { and the following is invalid: ```graphql counter-example -fragment definedOnImplementorsButNotInterface on Pet { +fragment definedOnImplementersButNotInterface on Pet { nickname } ``` @@ -446,7 +446,8 @@ SameResponseShape(fieldA, fieldB): - If {typeA} or {typeB} is Scalar or Enum: - If {typeA} and {typeB} are the same type return {true}, otherwise return {false}. -- Assert: {typeA} and {typeB} are both composite types. +- Assert: {typeA} is an object, union or interface type. +- Assert: {typeB} is an object, union or interface type. - Let {mergedSet} be the result of adding the selection set of {fieldA} and the selection set of {fieldB}. - Let {fieldsForName} be the set of selections with a given response name in @@ -455,6 +456,9 @@ SameResponseShape(fieldA, fieldB): - If {SameResponseShape(subfieldA, subfieldB)} is {false}, return {false}. - Return {true}. +Note: In prior versions of the spec the term "composite" was used to signal a +type that is either an Object, Interface or Union type. + **Explanatory Text** If multiple field selections with the same response names are encountered during @@ -910,7 +914,7 @@ fragment inlineNotExistingType on Dog { } ``` -#### Fragments on Composite Types +#### Fragments on Object, Interface or Union Types **Formal Specification** diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e43a395cd..196d75434 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -4,18 +4,21 @@ A GraphQL service generates a response from a request via execution. :: A _request_ for execution consists of a few pieces of information: -- The schema to use, typically solely provided by the GraphQL service. -- A {Document} which must contain GraphQL {OperationDefinition} and may contain - {FragmentDefinition}. -- Optionally: The name of the Operation in the Document to execute. -- Optionally: Values for any Variables defined by the Operation. -- An initial value corresponding to the root type being executed. Conceptually, - an initial value represents the "universe" of data available via a GraphQL - Service. It is common for a GraphQL Service to always use the same initial - value for every request. - -Given this information, the result of {ExecuteRequest()} produces the response, -to be formatted according to the Response section below. +- {schema}: The schema to use, typically solely provided by the GraphQL service. +- {document}: A {Document} which must contain GraphQL {OperationDefinition} and + may contain {FragmentDefinition}. +- {operationName} (optional): The name of the Operation in the Document to + execute. +- {variableValues} (optional): Values for any Variables defined by the + Operation. +- {initialValue} (optional): An initial value corresponding to the root type + being executed. Conceptually, an initial value represents the "universe" of + data available via a GraphQL Service. It is common for a GraphQL Service to + always use the same initial value for every request. + +Given this information, the result of {ExecuteRequest(schema, document, +operationName, variableValues, initialValue)} produces the response, to be +formatted according to the Response section below. Note: GraphQL requests do not require any specific serialization format or transport mechanism. Message serialization and transport mechanisms should be @@ -284,11 +287,11 @@ subscription _selection set_ using that event as a root value. MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - Return a new event stream {responseStream} which yields events as follows: -- For each {event} on {sourceStream}: - - Let {response} be the result of running - {ExecuteSubscriptionEvent(subscription, schema, variableValues, event)}. - - Yield an event containing {response}. -- When {responseStream} completes: complete this event stream. + - For each {event} on {sourceStream}: + - Let {response} be the result of running + {ExecuteSubscriptionEvent(subscription, schema, variableValues, event)}. + - Yield an event containing {response}. + - When {sourceStream} completes: complete {responseStream}. ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index aef4359b1..bd9448293 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -23,7 +23,7 @@ request failed before execution, due to a syntax error, missing information, or validation error, this entry must not be present. The response map may also contain an entry with key `extensions`. This entry, if -set, must have a map as its value. This entry is reserved for implementors to +set, must have a map as its value. This entry is reserved for implementers to extend the protocol however they see fit, and hence there are no additional restrictions on its contents. @@ -203,7 +203,7 @@ be the same: GraphQL services may provide an additional entry to errors with key `extensions`. This entry, if set, must have a map as its value. This entry is -reserved for implementors to add additional information to errors however they +reserved for implementers to add additional information to errors however they see fit, and there are no additional restrictions on its contents. ```json example