Skip to content

Commit

Permalink
Merge pull request #6 from rintoj/fix/schema-management
Browse files Browse the repository at this point in the history
fix: improve schema manager and show an error when no schema is loaded
  • Loading branch information
rintoj authored Jul 1, 2024
2 parents b5c461e + ff03c26 commit dd8f413
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 76 deletions.
50 changes: 38 additions & 12 deletions src/diagnostic/hook-diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { NoDuplicateFieldName } from './rules/NoDuplicateFieldName'

const additionalRules = [NoDuplicateFieldName]

function toError(error: gql.GraphQLError, context: Pick<GraphQLContext, 'offset' | 'sourceFile'>) {
function toDiagnostic(
error: gql.GraphQLError,
context: Pick<GraphQLContext, 'offset' | 'sourceFile'>,
): Diagnostic[] {
const lineOffset = context?.offset?.line ?? 0
if (!error.nodes?.length) {
const { line, column } = error.locations?.[0] ?? { line: 1, column: 1 }
Expand All @@ -18,15 +21,17 @@ function toError(error: gql.GraphQLError, context: Pick<GraphQLContext, 'offset'
new Position(position.line + lineOffset - 1, position.character - 1),
new Position(position.line + lineOffset - 1, position.character),
)
return {
fileName: context.sourceFile.fileName,
range,
severity: DiagnosticSeverity.Error,
message: error.message,
code: [context.sourceFile.fileName, range.start.line + 1, range.start.character + 1].join(
':',
),
}
return [
{
fileName: context.sourceFile.fileName,
range,
severity: DiagnosticSeverity.Error,
message: error.message,
code: [context.sourceFile.fileName, range.start.line + 1, range.start.character + 1].join(
':',
),
},
]
}
return error.nodes.map(node => {
const range = getGQLNodeLocationRange(node, context.offset)
Expand All @@ -52,12 +57,33 @@ export function diagnoseGraphQLQuery(
const result = gql
.validate(schema, document)
.concat(additionalRules.flatMap(rule => rule(document, schema)))
return result.flatMap(error => toError(error, context))
return result.flatMap(error => toDiagnostic(error, context))
} catch (e) {
return [toError(e, context)].flat()
return toDiagnostic(e, context).flat()
}
}

export function diagnoseSchema(
sourceFile: ts.SourceFile,
schema: gql.GraphQLSchema | undefined,
): Diagnostic[] {
if (schema) return []
const variable = getGraphQLQueryVariable(sourceFile)
if (!variable) return []
const graphQLQueryString = getGQLContent(variable)
if (!graphQLQueryString || graphQLQueryString?.trim() === '') return []
const range = getTSNodeLocationRange(variable, sourceFile)
return [
{
fileName: sourceFile.fileName,
range,
severity: DiagnosticSeverity.Error,
message: `No schema. Run command "gql-assist.choose.schema" to configure schema`,
code: [sourceFile.fileName, range.start.line + 1, range.start.character + 1].join(':'),
},
]
}

export function diagnoseReactHook(
sourceFile: ts.SourceFile,
schema: gql.GraphQLSchema,
Expand Down
92 changes: 28 additions & 64 deletions src/gql/schema-manger.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,60 @@
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'
import { loadSchema } from '@graphql-tools/load'
import { UrlLoader } from '@graphql-tools/url-loader'
import EventEmitter from 'events'
import { existsSync } from 'fs-extra'
import * as gql from 'graphql'
import { resolve } from 'path'
import { toNonNullArray } from 'tsds-tools'
import { GQLAssistConfig } from '../config'

enum SchemaEvent {
LOAD = 'load:Schema',
CHANGE = 'change:Schema',
REMOVE = 'remove:Schema',
}

export class SchemaManager {
private schemaFiles: string[] = []
private path: string | undefined
private schema: gql.GraphQLSchema | undefined
private changeEmitter = new EventEmitter()

private setSchema(path: string | undefined, schema: gql.GraphQLSchema | undefined) {
const currentSchema = this.schema
this.path = path
this.schema = schema
if (!currentSchema) {
this.changeEmitter.emit(SchemaEvent.LOAD)
} else if (!schema) {
this.changeEmitter.emit(SchemaEvent.REMOVE)
} else {
this.changeEmitter.emit(SchemaEvent.CHANGE)
}
return this
}

async loadSchemaFromUrl(url: string) {
return this.setSchema(url, await loadSchema(url, { loaders: [new UrlLoader()] }))
}

async loadSchemaFromFile(path: string) {
return this.setSchema(path, await loadSchema(path, { loaders: [new GraphQLFileLoader()] }))
}

async loadSchemaIfValid(path: string | undefined) {
if (!path) return this
if (path.startsWith('http')) {
return this.loadSchemaFromUrl(path)
}
if (this.schemaFiles.includes(path)) {
return this.loadSchemaFromFile(path)
}
return this
}

onDidLoad(callback: (schema: gql.GraphQLSchema | undefined, path: string | undefined) => any) {
const handler = () => callback(this.schema, this.path)
this.changeEmitter.on(SchemaEvent.LOAD, handler)
return { dispose: () => this.changeEmitter.off(SchemaEvent.LOAD, handler) }
getSchemaFSPath() {
return this.path
}

onDidChange(callback: (schema: gql.GraphQLSchema | undefined, path: string | undefined) => any) {
const handler = () => callback(this.schema, this.path)
this.changeEmitter.on(SchemaEvent.CHANGE, handler)
return { dispose: () => this.changeEmitter.off(SchemaEvent.CHANGE, handler) }
getSchema() {
return this.schema
}

onDidRemove(callback: (schema: gql.GraphQLSchema | undefined, path: string | undefined) => any) {
const handler = () => callback(this.schema, this.path)
this.changeEmitter.on(SchemaEvent.REMOVE, handler)
return { dispose: () => this.changeEmitter.off(SchemaEvent.REMOVE, handler) }
getSchemaFiles() {
return this.schemaFiles
}

findSchemaFiles(folders: string[], config: GQLAssistConfig) {
const possibleFileNames = toNonNullArray([...config.reactHook.schemaFileNames].flat())
const files = (this.schemaFiles = folders
return (this.schemaFiles = folders
.flatMap(folder => possibleFileNames.map(fileName => resolve(folder, fileName)))
.filter(path => existsSync(path)))
if (this.path && !files.includes(this.path)) {
this.setSchema(undefined, undefined)
}
return files
}

getSchemaFSPath() {
return this.path
async loadSchemaFromUrl(url: string) {
this.schema = await loadSchema(url, { loaders: [new UrlLoader()] })
this.path = url
return this
}

getSchemaFiles() {
return this.schemaFiles
async loadSchemaFromFile(path: string) {
this.schema = await loadSchema(path, { loaders: [new GraphQLFileLoader()] })
this.path = path
return this
}

getSchema() {
return this.schema
async loadSchema(pathOrUrl: string | undefined) {
if (pathOrUrl?.startsWith('http')) {
await this.loadSchemaFromUrl(pathOrUrl)
} else if (pathOrUrl) {
await this.loadSchemaFromFile(pathOrUrl)
}
return this
}

removeSchema() {
this.schema = undefined
this.path = undefined
return this
}
}

0 comments on commit dd8f413

Please sign in to comment.