Skip to content

feat: introduce graphql linting#2856

Open
tatomyr wants to merge 19 commits into
mainfrom
feat/lint-graphql
Open

feat: introduce graphql linting#2856
tatomyr wants to merge 19 commits into
mainfrom
feat/lint-graphql

Conversation

@tatomyr

@tatomyr tatomyr commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

What/Why/How?

Added experimental graphql linting.

Reference

Testing

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
New lint code path and graphql dependency touch core lint/resolve/config; behavior is experimental but isolated from OpenAPI bundle/ref resolution for GraphQL files.

Overview
Adds experimental lint support for GraphQL SDL (.graphql / .gql) in Redocly CLI and @redocly/openapi-core, wired like other spec types but with a dedicated AST pipeline using the graphql package.

Lint path: .graphql/.gql files skip YAML parsing; lintDocument routes them to lintGraphqlDocument, which parses SDL, runs built-in and configurable rules via AST visitors, and reports line/column locations. GraphQL SDL is excluded from bundle $ref resolution.

Rules & config: New graphql spec version with graphqlRules in presets (struct, no-unused-types, type-description) plus configurable rule/* assertions targeting GraphQL AST kinds (ObjectTypeDefinition, etc.). no-unresolved-refs is omitted from the GraphQL ruleset. Plugins may expose graphql rules; CLI checkIfRulesetExist counts graphql rules.

Docs & release: New lint-graphql guide, lint command/docs updates, sidebar entry, changeset (minor for core + cli). Config validation for invalid subject.type now links to docs instead of dumping huge enum lists when values are large.

Reviewed by Cursor Bugbot for commit b69ae8e. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Jun 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b69ae8e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/openapi-core Minor
@redocly/cli Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@tatomyr tatomyr self-assigned this Jun 5, 2026
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 81.71% (🎯 81%) 7587 / 9285
🔵 Statements 81.06% (🎯 80%) 7894 / 9738
🔵 Functions 85.01% (🎯 84%) 1526 / 1795
🔵 Branches 73.27% (🎯 72%) 5122 / 6990
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/utils/miscellaneous.ts 61.67% 58.2% 78.04% 60.97% 75, 140, 197-247, 273-274, 284-285, 300, 304, 307, 313, 317-320, 328-361, 372, 397, 406, 414-452, 567, 576
packages/core/src/detect-spec.ts 95.34% 95.31% 100% 95.34% 32, 40
packages/core/src/lint.ts 91.89% 85.71% 100% 91.66% 108-119, 142
packages/core/src/oas-types.ts 100% 100% 100% 100%
packages/core/src/resolve.ts 95.34% 94.69% 100% 95.2% 94, 141, 285, 460-468
packages/core/src/bundle/bundle-visitor.ts 72.72% 68.87% 100% 72.72% 37, 41-49, 60-64, 71, 80, 88, 93-118, 188-200, 217-218, 236-237, 251-252, 285
packages/core/src/config/all.ts 100% 100% 100% 100%
packages/core/src/config/builtIn.ts 100% 100% 100% 100%
packages/core/src/config/config-resolvers.ts 76.21% 60.98% 94.11% 77.01% 73, 101-104, 154, 193, 201, 257, 268, 277, 290, 300, 303-307, 312-321, 345-347, 357, 360, 363, 366, 369, 372, 385-387, 391, 397, 400, 403-406, 409-412, 415-418, 432-434, 441, 444, 447, 450, 453, 456, 481-483, 488-494
packages/core/src/config/config.ts 62.96% 64.58% 75.6% 63.09% 179-209, 235, 239, 243-264, 296, 314-334, 418-448
packages/core/src/config/minimal.ts 100% 100% 100% 100%
packages/core/src/config/recommended-strict.ts 100% 100% 100% 100%
packages/core/src/config/recommended.ts 100% 100% 100% 100%
packages/core/src/config/rules.ts 100% 100% 100% 100%
packages/core/src/config/spec.ts 100% 100% 100% 100%
packages/core/src/config/utils.ts 97.4% 81.81% 100% 98.68% 35, 84-86
packages/core/src/graphql/assertions.ts 95% 87.23% 100% 98.11% 39, 42, 63-65
packages/core/src/graphql/detect-graphql.ts 100% 100% 100% 100%
packages/core/src/graphql/lint-graphql.ts 95.23% 50% 100% 95% 32
packages/core/src/graphql/visitor.ts 92% 81.48% 100% 100% 54, 111
packages/core/src/rules/utils.ts 92% 88.7% 100% 91.3% 65, 99, 109-113, 129, 140-144, 237
packages/core/src/rules/common/struct.ts 97.22% 91.78% 100% 97.01% 77-81, 188-192
packages/core/src/rules/common/assertions/utils.ts 84% 65.51% 69.23% 88.29% 50, 54, 58, 83-85, 89-91, 142, 163-165, 172, 184-192, 252, 272-273, 289
packages/core/src/rules/graphql/index.ts 100% 100% 100% 100%
packages/core/src/rules/graphql/no-unused-types.ts 100% 73.07% 100% 100%
packages/core/src/rules/graphql/struct.ts 66.66% 33.33% 75% 66.66% 18, 27, 32-42
packages/core/src/rules/graphql/type-description.ts 100% 100% 100% 100%
packages/core/src/types/redocly-yaml.ts 92.92% 84.9% 100% 92.7% 412, 444, 450, 494-501, 503, 662-667, 670-675
Generated in workflow #10342 for commit b69ae8e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x (Fastest) ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)
cli-next ▓ 1.02x ± 0.01 ▓ 1.01x ± 0.01 ▓▓ 1.04x ± 0.01

Comment thread packages/core/src/rules/graphql/type-pascal-case.ts Outdated
Comment thread .changeset/graphql-sdl-linting.md Outdated
'@redocly/cli': minor
---

Added initial support for linting GraphQL SDL schema files (`.graphql` / `.gql`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mark it as experimental for now

"operation-summary": "error",
},
"graphql": {
"no-empty-servers": "error",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are unused rules propagated from the root rules config section.

Comment thread packages/core/src/config/config.ts Outdated
Comment thread packages/core/src/lint-graphql.ts Outdated
import type { Config } from './config/index.js';
import { initRules } from './config/rules.js';
import { isGraphqlRef } from './graphql/extensions.js';
import { runGraphqlRules, type InitializedGraphqlRule } from './graphql/run.js';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use dynamic import here so it won't load for everyone and slow them down

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't noticed any perf degradation so far. Would you still like to have it imported dynamcally?

@tatomyr tatomyr requested a review from RomanHotsiy June 12, 2026 08:07
@tatomyr tatomyr force-pushed the feat/lint-graphql branch from 7f3750d to 7bd109c Compare June 12, 2026 08:15
@tatomyr tatomyr marked this pull request as ready for review June 12, 2026 08:15
@tatomyr tatomyr requested review from a team as code owners June 12, 2026 08:16
Comment thread packages/core/src/rules/graphql/no-unused-types.ts
Comment thread packages/core/src/graphql/visitor.ts
Comment thread packages/core/src/config/config.ts
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
Comment thread docs/@v2/guides/lint-graphql.md Outdated
@tatomyr tatomyr force-pushed the feat/lint-graphql branch 2 times, most recently from 8723a0d to 73e2aff Compare June 16, 2026 08:18
Comment thread packages/core/src/graphql/assertions.ts
Comment thread packages/core/src/graphql/lint-graphql.ts
@tatomyr tatomyr force-pushed the feat/lint-graphql branch from 73e2aff to 6bdd8fe Compare June 16, 2026 14:35
Comment thread docs/@v2/guides/lint-graphql.md Outdated
## GraphQL rules

To expand the linting checks for a GraphQL schema, enable the built-in GraphQL rules.
Unlike the shared `struct` rule (configured under `rules`), GraphQL-specific built-in rules are configured under the `graphqlRules` section.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have mismatch in docs. We can also configure graphQL specific rules under rules section, you are listed this below.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved.

for (const kind of Object.keys(visitor) as Array<keyof GraphqlVisitor>) {
const handler = visitor[kind];
if (!handler) continue;
const enter = typeof handler === 'function' ? handler : handler.enter;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss the skip visitor or you leave it for the future implementation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found a proper use case for it in GraphQL. We can implement it later if needed.

Comment thread packages/cli/package.json Outdated
"cookie": "^0.7.2",
"dotenv": "16.4.7",
"glob": "^13.0.5",
"graphql": "^16.14.1",

@AlbinaBlazhko17 AlbinaBlazhko17 Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found where we use this package. Could you please explain the purpose of this package?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you mean in the CLI package? Yes, it was a transitive one, not needed anymore.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2b29c2e. Configure here.

Comment thread packages/core/src/config/config.ts
@tatomyr tatomyr force-pushed the feat/lint-graphql branch from 2b29c2e to c580a1b Compare June 17, 2026 06:10
config: Config;
}): NormalizedProblem[] {
const { document, config } = opts;
const source = document.source;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a guard for the empty GraphQL file to return Unexpected <EOF> error.

Image

Comment thread packages/core/src/lint.ts

// GraphQL SDL is not a JSON/YAML tree, so it runs through a separate engine.
if (isGraphqlRef(document.source.absoluteRef)) {
return lintGraphqlDocument({ document, config });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, that we can add dynamic imports for the GraphQL in the lint command, because now we have esbuild and chunks, so the dynamic import should go to the separate chunk. It should decrease the influence of the GraphQL on the performance.

@@ -481,6 +487,7 @@ function createAssertionDefinitionSubject(nodeNames: string[]): NodeType {
type: {
enum: [...new Set(['any', ...nodeNames, 'SpecExtension'])],
description: 'REQUIRED. Locates the OpenAPI node type that the lint command evaluates.',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we add assertions for the GraphQL, do we need to add GraphQL mention there? Or currently it's just an experimental feature?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants