From ae6baea8d7433655a3f05c66da340ba9b4e2c65b Mon Sep 17 00:00:00 2001 From: jdecroock Date: Wed, 3 Apr 2024 20:47:58 +0200 Subject: [PATCH 01/20] check wip --- examples/example-pokemon-api/package.json | 1 + .../src/components/PokemonList.tsx | 1 + .../example-pokemon-api/src/graphql-env.d.ts | 433 +++++++++++++++++- examples/example-pokemon-api/test.mjs | 51 +++ packages/internal/package.json | 8 +- packages/internal/src/diagnostics/index.ts | 51 +++ pnpm-lock.yaml | 65 ++- 7 files changed, 568 insertions(+), 42 deletions(-) create mode 100644 examples/example-pokemon-api/test.mjs create mode 100644 packages/internal/src/diagnostics/index.ts diff --git a/examples/example-pokemon-api/package.json b/examples/example-pokemon-api/package.json index 740ef858..e0dac007 100644 --- a/examples/example-pokemon-api/package.json +++ b/examples/example-pokemon-api/package.json @@ -10,6 +10,7 @@ "gql.tada": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "ts-morph": "^22.0.0", "urql": "^4.0.6" }, "devDependencies": { diff --git a/examples/example-pokemon-api/src/components/PokemonList.tsx b/examples/example-pokemon-api/src/components/PokemonList.tsx index d06f4451..e4240b7a 100644 --- a/examples/example-pokemon-api/src/components/PokemonList.tsx +++ b/examples/example-pokemon-api/src/components/PokemonList.tsx @@ -7,6 +7,7 @@ const PokemonsQuery = graphql(` query Pokemons ($limit: Int = 10) { pokemons(limit: $limit) { id + f ...PokemonItem } } diff --git a/examples/example-pokemon-api/src/graphql-env.d.ts b/examples/example-pokemon-api/src/graphql-env.d.ts index 59df4aaa..8d524386 100644 --- a/examples/example-pokemon-api/src/graphql-env.d.ts +++ b/examples/example-pokemon-api/src/graphql-env.d.ts @@ -10,23 +10,422 @@ * instead save to a .ts instead of a .d.ts file. */ export type introspection = { - query: 'Query'; - mutation: never; - subscription: never; - types: { - 'Attack': { kind: 'OBJECT'; name: 'Attack'; fields: { 'damage': { name: 'damage'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; } }; }; }; - 'Int': unknown; - 'String': unknown; - 'AttacksConnection': { kind: 'OBJECT'; name: 'AttacksConnection'; fields: { 'fast': { name: 'fast'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; 'special': { name: 'special'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; }; }; - 'EvolutionRequirement': { kind: 'OBJECT'; name: 'EvolutionRequirement'; fields: { 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'Pokemon': { kind: 'OBJECT'; name: 'Pokemon'; fields: { 'attacks': { name: 'attacks'; type: { kind: 'OBJECT'; name: 'AttacksConnection'; ofType: null; } }; 'classification': { name: 'classification'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'evolutionRequirements': { name: 'evolutionRequirements'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EvolutionRequirement'; ofType: null; }; } }; 'evolutions': { name: 'evolutions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; 'fleeRate': { name: 'fleeRate'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'height': { name: 'height'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'maxCP': { name: 'maxCP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxHP': { name: 'maxHP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'resistant': { name: 'resistant'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'types': { name: 'types'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weaknesses': { name: 'weaknesses'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weight': { name: 'weight'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; }; }; - 'Float': unknown; - 'ID': unknown; - 'PokemonDimension': { kind: 'OBJECT'; name: 'PokemonDimension'; fields: { 'maximum': { name: 'maximum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'minimum': { name: 'minimum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'PokemonType': { kind: 'ENUM'; name: 'PokemonType'; type: 'Bug' | 'Dark' | 'Dragon' | 'Electric' | 'Fairy' | 'Fighting' | 'Fire' | 'Flying' | 'Ghost' | 'Grass' | 'Ground' | 'Ice' | 'Normal' | 'Poison' | 'Psychic' | 'Rock' | 'Steel' | 'Water'; }; - 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'pokemon': { name: 'pokemon'; type: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; } }; 'pokemons': { name: 'pokemons'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; }; }; - 'Boolean': unknown; - }; + "__schema": { + "queryType": { + "name": "Query" + }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Attack", + "fields": [ + { + "name": "damage", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "args": [] + }, + { + "name": "name", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "args": [] + }, + { + "name": "type", + "type": { + "kind": "ENUM", + "name": "PokemonType", + "ofType": null + }, + "args": [] + } + ], + "interfaces": [] + }, + { + "kind": "SCALAR", + "name": "Int" + }, + { + "kind": "SCALAR", + "name": "String" + }, + { + "kind": "OBJECT", + "name": "AttacksConnection", + "fields": [ + { + "name": "fast", + "type": { + "kind": "LIST", + "ofType": { + "kind": "OBJECT", + "name": "Attack", + "ofType": null + } + }, + "args": [] + }, + { + "name": "special", + "type": { + "kind": "LIST", + "ofType": { + "kind": "OBJECT", + "name": "Attack", + "ofType": null + } + }, + "args": [] + } + ], + "interfaces": [] + }, + { + "kind": "OBJECT", + "name": "EvolutionRequirement", + "fields": [ + { + "name": "amount", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "args": [] + }, + { + "name": "name", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "args": [] + } + ], + "interfaces": [] + }, + { + "kind": "OBJECT", + "name": "Pokemon", + "fields": [ + { + "name": "attacks", + "type": { + "kind": "OBJECT", + "name": "AttacksConnection", + "ofType": null + }, + "args": [] + }, + { + "name": "classification", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "args": [] + }, + { + "name": "evolutionRequirements", + "type": { + "kind": "LIST", + "ofType": { + "kind": "OBJECT", + "name": "EvolutionRequirement", + "ofType": null + } + }, + "args": [] + }, + { + "name": "evolutions", + "type": { + "kind": "LIST", + "ofType": { + "kind": "OBJECT", + "name": "Pokemon", + "ofType": null + } + }, + "args": [] + }, + { + "name": "fleeRate", + "type": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + }, + "args": [] + }, + { + "name": "height", + "type": { + "kind": "OBJECT", + "name": "PokemonDimension", + "ofType": null + }, + "args": [] + }, + { + "name": "id", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "args": [] + }, + { + "name": "maxCP", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "args": [] + }, + { + "name": "maxHP", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "args": [] + }, + { + "name": "name", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "args": [] + }, + { + "name": "resistant", + "type": { + "kind": "LIST", + "ofType": { + "kind": "ENUM", + "name": "PokemonType", + "ofType": null + } + }, + "args": [] + }, + { + "name": "types", + "type": { + "kind": "LIST", + "ofType": { + "kind": "ENUM", + "name": "PokemonType", + "ofType": null + } + }, + "args": [] + }, + { + "name": "weaknesses", + "type": { + "kind": "LIST", + "ofType": { + "kind": "ENUM", + "name": "PokemonType", + "ofType": null + } + }, + "args": [] + }, + { + "name": "weight", + "type": { + "kind": "OBJECT", + "name": "PokemonDimension", + "ofType": null + }, + "args": [] + } + ], + "interfaces": [] + }, + { + "kind": "SCALAR", + "name": "Float" + }, + { + "kind": "SCALAR", + "name": "ID" + }, + { + "kind": "OBJECT", + "name": "PokemonDimension", + "fields": [ + { + "name": "maximum", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "args": [] + }, + { + "name": "minimum", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "args": [] + } + ], + "interfaces": [] + }, + { + "kind": "ENUM", + "name": "PokemonType", + "enumValues": [ + { + "name": "Bug" + }, + { + "name": "Dark" + }, + { + "name": "Dragon" + }, + { + "name": "Electric" + }, + { + "name": "Fairy" + }, + { + "name": "Fighting" + }, + { + "name": "Fire" + }, + { + "name": "Flying" + }, + { + "name": "Ghost" + }, + { + "name": "Grass" + }, + { + "name": "Ground" + }, + { + "name": "Ice" + }, + { + "name": "Normal" + }, + { + "name": "Poison" + }, + { + "name": "Psychic" + }, + { + "name": "Rock" + }, + { + "name": "Steel" + }, + { + "name": "Water" + } + ] + }, + { + "kind": "OBJECT", + "name": "Query", + "fields": [ + { + "name": "pokemon", + "type": { + "kind": "OBJECT", + "name": "Pokemon", + "ofType": null + }, + "args": [ + { + "name": "id", + "type": { + "kind": "NON_NULL", + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ] + }, + { + "name": "pokemons", + "type": { + "kind": "LIST", + "ofType": { + "kind": "OBJECT", + "name": "Pokemon", + "ofType": null + } + }, + "args": [ + { + "name": "limit", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "name": "skip", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + } + ] + } + ], + "interfaces": [] + }, + { + "kind": "SCALAR", + "name": "Boolean" + } + ], + "directives": [] + } }; import * as gqlTada from 'gql.tada'; diff --git a/examples/example-pokemon-api/test.mjs b/examples/example-pokemon-api/test.mjs new file mode 100644 index 00000000..e2a16cc4 --- /dev/null +++ b/examples/example-pokemon-api/test.mjs @@ -0,0 +1,51 @@ +import { Project, ts } from "ts-morph"; +import init from '@0no-co/graphqlsp' +import path from 'path'; +import fs from 'fs'; + +export async function check() { + const project = new Project({ + tsConfigFilePath: './tsconfig.json' + }); + const plugin = init({ + typescript: ts + }) + + const languageService = project.getLanguageService() + const createdPlugin = plugin.create({ + // TODO: add in config + config: { + schema: './schema.graphql', + shouldCheckForColocatedFragments: false, + trackFieldUsage: false + }, + languageService: { + ...languageService, + getProgram: () => { + const program = project.getProgram() + return { + ...program, + getTypeChecker: () => project.getTypeChecker(), + getSourceFile: (s) => project.getSourceFile(s) + } + }, + getSemanticDiagnostics: () => [], + }, + languageServiceHost: {}, + project: { + getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), + readFile: (p) => { + return fs.readFileSync(p); + }, + projectService: { + logger: console + } + }, + serverHost: {} + }) + + const diag = createdPlugin.getSemanticDiagnostics('./src/components/PokemonList.tsx') + console.log('diag', diag) +} + +check(); diff --git a/packages/internal/package.json b/packages/internal/package.json index bb0343ff..8ab2c226 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -46,14 +46,16 @@ "@urql/exchange-retry": "^1.2.1", "@urql/introspection": "^1.0.3", "json5": "^2.2.3", + "graphql": "^16.8.1", "rollup": "^4.9.4", "sade": "^1.8.1", "type-fest": "^4.10.2", - "typescript": "^5.3.3", - "graphql": "^16.8.1" + "typescript": "^5.3.3" }, "dependencies": { - "@0no-co/graphql.web": "^1.0.5" + "@0no-co/graphql.web": "^1.0.5", + "@0no-co/graphqlsp": "^1.0.0", + "ts-morph": "^22.0.0" }, "peerDependencies": { "typescript": "^5.0.0", diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts new file mode 100644 index 00000000..8a8de008 --- /dev/null +++ b/packages/internal/src/diagnostics/index.ts @@ -0,0 +1,51 @@ +import { Project, ts } from "ts-morph"; +import init from '@0no-co/graphqlsp' +import path from 'path'; +import fs from 'fs'; + +export async function check() { + const project = new Project({ + tsConfigFilePath: './tsconfig.json' + }); + const plugin = init({ + typescript: ts as any + }) + + const languageService = project.getLanguageService() + const createdPlugin = plugin.create({ + // TODO: add in config + config: { + schema: './schema.graphql', + shouldCheckForColocatedFragments: false, + trackFieldUsage: false + }, + languageService: { + ...languageService, + getProgram: (() => { + const program = project.getProgram() + return { + ...program, + getTypeChecker: () => project.getTypeChecker(), + getSourceFile: (s) => project.getSourceFile(s) + } + }) as any, + getSemanticDiagnostics: () => [], + } as any, + languageServiceHost: {} as any, + project: { + getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), + readFile: ((p) => { + return fs.readFileSync(p); + }) as any, + projectService: { + logger: console as any + } as any + } as any, + serverHost: {} as any + }) + + const diag = createdPlugin.getSemanticDiagnostics('./src/components/PokemonList.tsx') + console.log('diag', diag) +} + +check(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05a85a56..2844cb53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + ts-morph: + specifier: ^22.0.0 + version: 22.0.0 urql: specifier: ^4.0.6 version: 4.0.6(graphql@16.8.1)(react@18.2.0) @@ -181,6 +184,12 @@ importers: '@0no-co/graphql.web': specifier: ^1.0.5 version: 1.0.5(graphql@16.8.1) + '@0no-co/graphqlsp': + specifier: ^1.0.0 + version: 1.0.0 + ts-morph: + specifier: ^22.0.0 + version: 22.0.0 devDependencies: '@types/node': specifier: ^20.11.0 @@ -274,6 +283,14 @@ packages: dependencies: graphql: 16.8.1 + /@0no-co/graphqlsp@1.0.0: + resolution: {integrity: sha512-uzO2wfP4krHRbyH8wRhTvz7irq7PhmCedacgtt+rZ8EHzZ8KRLQGygvibotcDU+4vIaEIUr+Dt7uE9XGcbglvg==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /@0no-co/graphqlsp@1.5.0: resolution: {integrity: sha512-NGMUwzj7m6DfIqudZxfTfUEmzKR2n9xphPssCKqkGsMEKe9qIzrNqLpXXwzXTWrGBS5FBELW+TXGUyTa0v1LAw==} dependencies: @@ -1582,12 +1599,10 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -1595,7 +1610,6 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 - dev: true /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -1916,6 +1930,15 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@ts-morph/common@0.23.0: + resolution: {integrity: sha512-m7Lllj9n/S6sOkCkRftpM7L24uvmfXQFedlW/4hENcuJH1HHm9u5EgxZb9uVjQSCGrbBWBkOGgcTxNg36r6ywA==} + dependencies: + fast-glob: 3.3.2 + minimatch: 9.0.3 + mkdirp: 3.0.1 + path-browserify: 1.0.1 + dev: false + /@types/babel__core@7.20.5: resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: @@ -2665,7 +2688,6 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -2685,14 +2707,12 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true /breakword@1.0.6: resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} @@ -2855,6 +2875,10 @@ packages: engines: {node: '>=0.8'} dev: true + /code-block-writer@13.0.1: + resolution: {integrity: sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==} + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -3442,7 +3466,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -3456,7 +3479,6 @@ packages: resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} dependencies: reusify: 1.0.4 - dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -3470,7 +3492,6 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -3636,7 +3657,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -3935,7 +3955,6 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -3959,7 +3978,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} @@ -3980,7 +3998,6 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -4501,7 +4518,6 @@ packages: /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true /micromark-core-commonmark@2.0.0: resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} @@ -4684,7 +4700,6 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} @@ -4719,7 +4734,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -4748,6 +4762,12 @@ packages: engines: {node: '>= 8.0.0'} dev: true + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: @@ -4983,7 +5003,6 @@ packages: /path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - dev: true /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} @@ -5052,7 +5071,6 @@ packages: /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true /pidtree@0.3.1: resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} @@ -5165,7 +5183,6 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} @@ -5332,7 +5349,6 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true /rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} @@ -5432,7 +5448,6 @@ packages: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true /sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} @@ -5900,7 +5915,6 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5930,6 +5944,13 @@ packages: tslib: 2.6.2 dev: false + /ts-morph@22.0.0: + resolution: {integrity: sha512-M9MqFGZREyeb5fTl6gNHKZLqBQA0TjA1lea+CR48R8EBTDuWrNqW6ccC5QvjNR4s6wDumD3LTCjOFSp9iwlzaw==} + dependencies: + '@ts-morph/common': 0.23.0 + code-block-writer: 13.0.1 + dev: false + /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} From 70e79364ed06abfca71c9dedbcc92cdbf581d1ac Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 08:29:03 +0200 Subject: [PATCH 02/20] make it work --- examples/example-pokemon-api/test.mjs | 51 --------- packages/internal/src/diagnostics/index.ts | 124 +++++++++++++-------- 2 files changed, 80 insertions(+), 95 deletions(-) delete mode 100644 examples/example-pokemon-api/test.mjs diff --git a/examples/example-pokemon-api/test.mjs b/examples/example-pokemon-api/test.mjs deleted file mode 100644 index e2a16cc4..00000000 --- a/examples/example-pokemon-api/test.mjs +++ /dev/null @@ -1,51 +0,0 @@ -import { Project, ts } from "ts-morph"; -import init from '@0no-co/graphqlsp' -import path from 'path'; -import fs from 'fs'; - -export async function check() { - const project = new Project({ - tsConfigFilePath: './tsconfig.json' - }); - const plugin = init({ - typescript: ts - }) - - const languageService = project.getLanguageService() - const createdPlugin = plugin.create({ - // TODO: add in config - config: { - schema: './schema.graphql', - shouldCheckForColocatedFragments: false, - trackFieldUsage: false - }, - languageService: { - ...languageService, - getProgram: () => { - const program = project.getProgram() - return { - ...program, - getTypeChecker: () => project.getTypeChecker(), - getSourceFile: (s) => project.getSourceFile(s) - } - }, - getSemanticDiagnostics: () => [], - }, - languageServiceHost: {}, - project: { - getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), - readFile: (p) => { - return fs.readFileSync(p); - }, - projectService: { - logger: console - } - }, - serverHost: {} - }) - - const diag = createdPlugin.getSemanticDiagnostics('./src/components/PokemonList.tsx') - console.log('diag', diag) -} - -check(); diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index 8a8de008..7f7eef64 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -1,51 +1,87 @@ -import { Project, ts } from "ts-morph"; -import init from '@0no-co/graphqlsp' +import { Project, ts } from 'ts-morph'; +import init from '@0no-co/graphqlsp'; import path from 'path'; import fs from 'fs'; export async function check() { - const project = new Project({ - tsConfigFilePath: './tsconfig.json' - }); - const plugin = init({ - typescript: ts as any - }) - - const languageService = project.getLanguageService() - const createdPlugin = plugin.create({ - // TODO: add in config - config: { - schema: './schema.graphql', - shouldCheckForColocatedFragments: false, - trackFieldUsage: false - }, - languageService: { - ...languageService, - getProgram: (() => { - const program = project.getProgram() - return { - ...program, - getTypeChecker: () => project.getTypeChecker(), - getSourceFile: (s) => project.getSourceFile(s) - } - }) as any, - getSemanticDiagnostics: () => [], - } as any, - languageServiceHost: {} as any, - project: { - getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), - readFile: ((p) => { - return fs.readFileSync(p); - }) as any, - projectService: { - logger: console as any - } as any - } as any, - serverHost: {} as any - }) - - const diag = createdPlugin.getSemanticDiagnostics('./src/components/PokemonList.tsx') - console.log('diag', diag) + const project = new Project({ + tsConfigFilePath: './tsconfig.json', + }); + + const plugin = init({ + typescript: ts as any, + }); + + const languageService = project.getLanguageService(); + const createdPlugin = plugin.create({ + // TODO: add in config + config: { + schema: './schema.graphql', + }, + languageService: { + getReferencesAtPosition: (filename, position) => { + return languageService.compilerObject.getReferencesAtPosition(filename, position); + }, + getDefinitionAtPosition: (filename, position) => { + return languageService.compilerObject.getDefinitionAtPosition(filename, position); + }, + getProgram: () => { + const program = project.getProgram(); + return { + ...program, + getTypeChecker: () => project.getTypeChecker(), + getSourceFile: (s) => { + const source = project.getSourceFile(s); + return source && source.compilerNode; + }, + }; + }, + // This prevents us from exposing normal diagnostics + getSemanticDiagnostics: () => [], + } as any, + languageServiceHost: {} as any, + project: { + getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), + readFile: (p) => { + return fs.readFileSync(p); + }, + projectService: { + logger: console, + }, + } as any, + serverHost: {} as any, + }); + + const sourceFiles = project.getSourceFiles(); + + const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles.flatMap((sourceFile) => { + const diag = createdPlugin.getSemanticDiagnostics(sourceFile.getFilePath()); + return diag.map((diag) => ({ + severity: + diag.category === ts.DiagnosticCategory.Error + ? 'error' + : diag.category === ts.DiagnosticCategory.Warning + ? 'warning' + : 'info', + message: diag.messageText as string, + start: diag.start || 0, + end: (diag.start || 0) + (diag.length || 0), + file: diag.file && diag.file.fileName, + })); + }); + printDiagnostics(allDiagnostics); +} + +interface FormattedDisplayableDiagnostic { + severity: 'error' | 'warning' | 'info'; + message: string; + start: number; + end: number; + file: string | undefined; +} + +function printDiagnostics(diagnostics: FormattedDisplayableDiagnostic[]) { + return diagnostics; } check(); From 2d2224328e93413ed07c69881dd33719ca8575e0 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 14:24:59 +0200 Subject: [PATCH 03/20] check --- packages/internal/package.json | 3 +- packages/internal/src/diagnostics/index.ts | 46 ++++++++++++++++------ packages/internal/src/index.ts | 1 + pnpm-lock.yaml | 38 ++++++++++++------ 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/packages/internal/package.json b/packages/internal/package.json index 8ab2c226..eaa1727f 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -54,8 +54,7 @@ }, "dependencies": { "@0no-co/graphql.web": "^1.0.5", - "@0no-co/graphqlsp": "^1.0.0", - "ts-morph": "^22.0.0" + "@0no-co/graphqlsp": "^1.8.0" }, "peerDependencies": { "typescript": "^5.0.0", diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index 7f7eef64..577ec88b 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -1,19 +1,26 @@ import { Project, ts } from 'ts-morph'; -import init from '@0no-co/graphqlsp'; +import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; import path from 'path'; import fs from 'fs'; +import { resolveTypeScriptRootDir } from '../resolve'; +import { load } from '../loaders'; + +// TODO: introduce severity filter +export async function check(): Promise { + const projectName = path.resolve(process.cwd(), 'tsconfig.json'); -export async function check() { const project = new Project({ tsConfigFilePath: './tsconfig.json', }); - const plugin = init({ + const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); + + init({ typescript: ts as any, }); const languageService = project.getLanguageService(); - const createdPlugin = plugin.create({ + const pluginCreateInfo = { // TODO: add in config config: { schema: './schema.graphql', @@ -50,12 +57,30 @@ export async function check() { }, } as any, serverHost: {} as any, - }); + }; const sourceFiles = project.getSourceFiles(); + const loader = load({ origin, rootPath }); + let schema; + try { + const loaderResult = await loader.load(); + schema = loaderResult && loaderResult.schema; + if (!schema) { + console.error(`Failed to load schema`); + return []; + } + } catch (error) { + console.error(`Failed to load schema: ${error}`); + return []; + } const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles.flatMap((sourceFile) => { - const diag = createdPlugin.getSemanticDiagnostics(sourceFile.getFilePath()); + const diag = + getGraphQLDiagnostics( + sourceFile.getFilePath(), + { current: schema, version: 1 }, + pluginCreateInfo + ) || []; return diag.map((diag) => ({ severity: diag.category === ts.DiagnosticCategory.Error @@ -69,7 +94,8 @@ export async function check() { file: diag.file && diag.file.fileName, })); }); - printDiagnostics(allDiagnostics); + + return allDiagnostics; } interface FormattedDisplayableDiagnostic { @@ -79,9 +105,3 @@ interface FormattedDisplayableDiagnostic { end: number; file: string | undefined; } - -function printDiagnostics(diagnostics: FormattedDisplayableDiagnostic[]) { - return diagnostics; -} - -check(); diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index 4a5dbc44..1f57e7c2 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -8,3 +8,4 @@ export { } from './introspection'; export { resolveTypeScriptRootDir } from './resolve'; +export { check } from './diagnostics'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2844cb53..7bbe666f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,11 +185,8 @@ importers: specifier: ^1.0.5 version: 1.0.5(graphql@16.8.1) '@0no-co/graphqlsp': - specifier: ^1.0.0 - version: 1.0.0 - ts-morph: - specifier: ^22.0.0 - version: 22.0.0 + specifier: ^1.8.0 + version: 1.8.0(typescript@5.4.2) devDependencies: '@types/node': specifier: ^20.11.0 @@ -283,22 +280,27 @@ packages: dependencies: graphql: 16.8.1 - /@0no-co/graphqlsp@1.0.0: - resolution: {integrity: sha512-uzO2wfP4krHRbyH8wRhTvz7irq7PhmCedacgtt+rZ8EHzZ8KRLQGygvibotcDU+4vIaEIUr+Dt7uE9XGcbglvg==} + /@0no-co/graphqlsp@1.5.0: + resolution: {integrity: sha512-NGMUwzj7m6DfIqudZxfTfUEmzKR2n9xphPssCKqkGsMEKe9qIzrNqLpXXwzXTWrGBS5FBELW+TXGUyTa0v1LAw==} dependencies: + json5: 2.2.3 node-fetch: 2.7.0 transitivePeerDependencies: - encoding - dev: false + dev: true - /@0no-co/graphqlsp@1.5.0: - resolution: {integrity: sha512-NGMUwzj7m6DfIqudZxfTfUEmzKR2n9xphPssCKqkGsMEKe9qIzrNqLpXXwzXTWrGBS5FBELW+TXGUyTa0v1LAw==} + /@0no-co/graphqlsp@1.8.0(typescript@5.4.2): + resolution: {integrity: sha512-DVET7krt1doddpSDg97t41mZZru2P/CWv22JXG4N1oFo1A1KFVGdka/chnn4pmB7/FqRCRUJARGvZNwwb1EJjw==} + peerDependencies: + typescript: ^5.3.3 dependencies: - json5: 2.2.3 + '@gql.tada/internal': 0.1.2(graphql@16.8.1)(typescript@5.4.2) + graphql: 16.8.1 node-fetch: 2.7.0 + typescript: 5.4.2 transitivePeerDependencies: - encoding - dev: true + dev: false /@0no-co/typescript.js@5.3.2-2: resolution: {integrity: sha512-IGQZZ7vcVD/GOUKLJckpQiq8F5raQZLR7kOVhxN5nHOywl1dB9EFMA7FJkyNoCEig7EkCb291X8FswMwwrz9yg==} @@ -1451,6 +1453,17 @@ packages: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: true + /@gql.tada/internal@0.1.2(graphql@16.8.1)(typescript@5.4.2): + resolution: {integrity: sha512-8I4Z1zxYYGK66FWdB3yIZBn3cITLPnciEgjChp3K2+Ha1e/AEBGtZv9AUlodraO/RZafDMkpFhoi+tMpluBjeg==} + peerDependencies: + graphql: ^16.8.1 + typescript: ^5.3.3 + dependencies: + '@0no-co/graphql.web': 1.0.5(graphql@16.8.1) + graphql: 16.8.1 + typescript: 5.4.2 + dev: false + /@graphql-typed-document-node/core@3.2.0(graphql@16.8.1): resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} peerDependencies: @@ -6081,7 +6094,6 @@ packages: resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} hasBin: true - dev: true /typescript@5.4.3: resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} From cc148642a14e961097515df562072abd68b97a7c Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 14:27:28 +0200 Subject: [PATCH 04/20] min severity filter --- packages/internal/src/diagnostics/index.ts | 65 ++++++++++++---------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index 577ec88b..a3fc94e8 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -5,8 +5,19 @@ import fs from 'fs'; import { resolveTypeScriptRootDir } from '../resolve'; import { load } from '../loaders'; -// TODO: introduce severity filter -export async function check(): Promise { +type Severity = 'error' | 'warning' | 'info'; +const severities: Severity[] = ['error', 'warning', 'info']; +interface FormattedDisplayableDiagnostic { + severity: Severity; + message: string; + start: number; + end: number; + file: string | undefined; +} + +export async function check( + minSeverity: Severity = 'error' +): Promise { const projectName = path.resolve(process.cwd(), 'tsconfig.json'); const project = new Project({ @@ -74,34 +85,28 @@ export async function check(): Promise { return []; } - const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles.flatMap((sourceFile) => { - const diag = - getGraphQLDiagnostics( - sourceFile.getFilePath(), - { current: schema, version: 1 }, - pluginCreateInfo - ) || []; - return diag.map((diag) => ({ - severity: - diag.category === ts.DiagnosticCategory.Error - ? 'error' - : diag.category === ts.DiagnosticCategory.Warning - ? 'warning' - : 'info', - message: diag.messageText as string, - start: diag.start || 0, - end: (diag.start || 0) + (diag.length || 0), - file: diag.file && diag.file.fileName, - })); - }); + const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles + .flatMap((sourceFile) => { + const diag = + getGraphQLDiagnostics( + sourceFile.getFilePath(), + { current: schema, version: 1 }, + pluginCreateInfo + ) || []; + return diag.map((diag) => ({ + severity: + diag.category === ts.DiagnosticCategory.Error + ? 'error' + : diag.category === ts.DiagnosticCategory.Warning + ? 'warning' + : 'info', + message: diag.messageText as string, + start: diag.start || 0, + end: (diag.start || 0) + (diag.length || 0), + file: diag.file && diag.file.fileName, + })); + }) + .filter((diag) => severities.indexOf(diag.severity) >= severities.indexOf(minSeverity)); return allDiagnostics; } - -interface FormattedDisplayableDiagnostic { - severity: 'error' | 'warning' | 'info'; - message: string; - start: number; - end: number; - file: string | undefined; -} From bf2a6037756594bcfba4b1504c3b026c40d2e5cd Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 14:34:18 +0200 Subject: [PATCH 05/20] config --- packages/internal/src/diagnostics/index.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index a3fc94e8..44542095 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -3,6 +3,7 @@ import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; import path from 'path'; import fs from 'fs'; import { resolveTypeScriptRootDir } from '../resolve'; +import type { SchemaOrigin } from '../loaders'; import { load } from '../loaders'; type Severity = 'error' | 'warning' | 'info'; @@ -15,7 +16,14 @@ interface FormattedDisplayableDiagnostic { file: string | undefined; } +type GraphQLSPConfig = { + name: string; + schema: SchemaOrigin; + tadaOutputLocation: string; +}; + export async function check( + config: GraphQLSPConfig, minSeverity: Severity = 'error' ): Promise { const projectName = path.resolve(process.cwd(), 'tsconfig.json'); @@ -32,10 +40,7 @@ export async function check( const languageService = project.getLanguageService(); const pluginCreateInfo = { - // TODO: add in config - config: { - schema: './schema.graphql', - }, + config, languageService: { getReferencesAtPosition: (filename, position) => { return languageService.compilerObject.getReferencesAtPosition(filename, position); @@ -71,7 +76,7 @@ export async function check( }; const sourceFiles = project.getSourceFiles(); - const loader = load({ origin, rootPath }); + const loader = load({ origin: config.schema, rootPath }); let schema; try { const loaderResult = await loader.load(); From 6da1f111073c7837910fadb41e44600544af05aa Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 14:58:09 +0200 Subject: [PATCH 06/20] fixes --- packages/internal/src/diagnostics/index.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index 44542095..9e07e9b2 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -82,12 +82,10 @@ export async function check( const loaderResult = await loader.load(); schema = loaderResult && loaderResult.schema; if (!schema) { - console.error(`Failed to load schema`); - return []; + throw new Error(`Failed to load schema`); } } catch (error) { - console.error(`Failed to load schema: ${error}`); - return []; + throw new Error(`Failed to load schema: ${error}`); } const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles @@ -99,12 +97,11 @@ export async function check( pluginCreateInfo ) || []; return diag.map((diag) => ({ - severity: - diag.category === ts.DiagnosticCategory.Error - ? 'error' - : diag.category === ts.DiagnosticCategory.Warning - ? 'warning' - : 'info', + severity: (diag.category === ts.DiagnosticCategory.Error + ? 'error' + : diag.category === ts.DiagnosticCategory.Warning + ? 'warning' + : 'info') as Severity, message: diag.messageText as string, start: diag.start || 0, end: (diag.start || 0) + (diag.length || 0), From 6f3d3332890a190258854d1a047e94dd11351023 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 14:58:54 +0200 Subject: [PATCH 07/20] fix --- packages/internal/src/diagnostics/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index 9e07e9b2..a67d5a97 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -108,7 +108,8 @@ export async function check( file: diag.file && diag.file.fileName, })); }) - .filter((diag) => severities.indexOf(diag.severity) >= severities.indexOf(minSeverity)); + // Filter out diagnostics below the minimum severity + .filter((diag) => severities.indexOf(diag.severity) <= severities.indexOf(minSeverity)); return allDiagnostics; } From a933faa051ce72aed2614e93529dec9e55e5c182 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 15:52:24 +0200 Subject: [PATCH 08/20] add cli command --- packages/cli-utils/src/index.ts | 69 +++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index bd434d05..728e15e0 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -6,7 +6,7 @@ import { printSchema } from 'graphql'; import type { GraphQLSchema } from 'graphql'; import type { TsConfigJson } from 'type-fest'; -import { resolveTypeScriptRootDir, load } from '@gql.tada/internal'; +import { resolveTypeScriptRootDir, load, check } from '@gql.tada/internal'; import { getGraphQLSPConfig } from './lsp'; import { ensureTadaIntrospection } from './tada'; @@ -72,31 +72,8 @@ export async function generateSchema( } export async function generateTadaTypes(shouldPreprocess = false, cwd: string = process.cwd()) { - const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); - - // TODO: Remove redundant read and move tsconfig.json handling to internal package - const root = (await resolveTypeScriptRootDir(tsconfigpath)) || cwd; - - let tsconfigContents: string; - try { - const file = path.resolve(root, 'tsconfig.json'); - tsconfigContents = await fs.readFile(file, 'utf-8'); - } catch (error) { - console.error('Failed to read tsconfig.json in current working directory.', error); - return; - } - - let tsConfig: TsConfigJson; - try { - tsConfig = parse(tsconfigContents) as TsConfigJson; - } catch (err) { - console.error(err); - return; - } - - const config = getGraphQLSPConfig(tsConfig); + const config = await getConfig(); if (!config) { - console.error(`Could not find a "@0no-co/graphqlsp" plugin in your tsconfig.`); return; } @@ -155,8 +132,50 @@ async function main() { const shouldPreprocess = !options['disable-preprocessing'] && options['disable-preprocessing'] !== 'false'; return generateTadaTypes(shouldPreprocess); + }) + .command('check') + .option('--severity', 'The minimum severity of diagnostics to display.') + .action(async (opts) => { + const config = await getConfig(); + if (!config) { + return; + } + check(config, opts.severity || 'error'); }); prog.parse(process.argv); } +const getConfig = async () => { + const cwd = process.cwd(); + const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); + + // TODO: Remove redundant read and move tsconfig.json handling to internal package + const root = (await resolveTypeScriptRootDir(tsconfigpath)) || cwd; + + let tsconfigContents: string; + try { + const file = path.resolve(root, 'tsconfig.json'); + tsconfigContents = await fs.readFile(file, 'utf-8'); + } catch (error) { + console.error('Failed to read tsconfig.json in current working directory.', error); + return; + } + + let tsConfig: TsConfigJson; + try { + tsConfig = parse(tsconfigContents) as TsConfigJson; + } catch (err) { + console.error(err); + return; + } + + const config = getGraphQLSPConfig(tsConfig); + if (!config) { + console.error(`Could not find a "@0no-co/graphqlsp" plugin in your tsconfig.`); + return; + } + + return config; +}; + export default main; From 65b28c7d016ef8e2d484694406dcf4a6c897451e Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 15:53:09 +0200 Subject: [PATCH 09/20] add type --- packages/cli-utils/src/index.ts | 3 ++- packages/internal/package.json | 3 ++- pnpm-lock.yaml | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index 728e15e0..437549d8 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -8,6 +8,7 @@ import type { GraphQLSchema } from 'graphql'; import type { TsConfigJson } from 'type-fest'; import { resolveTypeScriptRootDir, load, check } from '@gql.tada/internal'; +import type { GraphQLSPConfig } from './lsp'; import { getGraphQLSPConfig } from './lsp'; import { ensureTadaIntrospection } from './tada'; @@ -145,7 +146,7 @@ async function main() { prog.parse(process.argv); } -const getConfig = async () => { +const getConfig = async (): Promise => { const cwd = process.cwd(); const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); diff --git a/packages/internal/package.json b/packages/internal/package.json index eaa1727f..c6dd6aa7 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -54,7 +54,8 @@ }, "dependencies": { "@0no-co/graphql.web": "^1.0.5", - "@0no-co/graphqlsp": "^1.8.0" + "@0no-co/graphqlsp": "^1.8.0", + "ts-morph": "^22.0.0" }, "peerDependencies": { "typescript": "^5.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bbe666f..1bb54de6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,6 +187,9 @@ importers: '@0no-co/graphqlsp': specifier: ^1.8.0 version: 1.8.0(typescript@5.4.2) + ts-morph: + specifier: ^22.0.0 + version: 22.0.0 devDependencies: '@types/node': specifier: ^20.11.0 From 9d434991cd0daed1ce4c04151835d835db3fb152 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Thu, 4 Apr 2024 20:50:34 +0200 Subject: [PATCH 10/20] leverage lina nd col --- packages/internal/src/diagnostics/index.ts | 50 ++++++++++++++++------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index a67d5a97..aafc5766 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -11,8 +11,8 @@ const severities: Severity[] = ['error', 'warning', 'info']; interface FormattedDisplayableDiagnostic { severity: Severity; message: string; - start: number; - end: number; + line: number; + col: number; file: string | undefined; } @@ -33,7 +33,6 @@ export async function check( }); const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); - init({ typescript: ts as any, }); @@ -96,20 +95,43 @@ export async function check( { current: schema, version: 1 }, pluginCreateInfo ) || []; - return diag.map((diag) => ({ - severity: (diag.category === ts.DiagnosticCategory.Error - ? 'error' - : diag.category === ts.DiagnosticCategory.Warning - ? 'warning' - : 'info') as Severity, - message: diag.messageText as string, - start: diag.start || 0, - end: (diag.start || 0) + (diag.length || 0), - file: diag.file && diag.file.fileName, - })); + return diag.map((diag) => { + const text = diag.file && diag.file.getText(); + const start = diag.start; + const [line, col] = getLineCol(text || '', start || 0); + return { + severity: (diag.category === ts.DiagnosticCategory.Error + ? 'error' + : diag.category === ts.DiagnosticCategory.Warning + ? 'warning' + : 'info') as Severity, + message: diag.messageText as string, + file: diag.file && diag.file.fileName, + line, + col, + }; + }); }) // Filter out diagnostics below the minimum severity .filter((diag) => severities.indexOf(diag.severity) <= severities.indexOf(minSeverity)); return allDiagnostics; } + +function getLineCol(text: string, start: number): [number, number] { + if (!text || !start) return [0, 0]; + + let counter = 0; + const parts = text.split('\n'); + for (let i = 0; i <= parts.length; i++) { + const line = parts[i]; + if (counter + line.length > start) { + return [i + 1, start - counter]; + } else { + counter = counter + (line.length + 1); + continue; + } + } + + return [0, 0]; +} From 9b78ee63c3896d53b55efc53094d9ad0f7bce064 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 21:04:54 +0200 Subject: [PATCH 11/20] finish it --- .../src/components/PokemonList.tsx | 1 - packages/cli-utils/src/index.ts | 54 ++++++++++++++++++- packages/internal/src/diagnostics/index.ts | 10 ++-- packages/internal/src/index.ts | 2 +- 4 files changed, 58 insertions(+), 9 deletions(-) diff --git a/examples/example-pokemon-api/src/components/PokemonList.tsx b/examples/example-pokemon-api/src/components/PokemonList.tsx index e4240b7a..d06f4451 100644 --- a/examples/example-pokemon-api/src/components/PokemonList.tsx +++ b/examples/example-pokemon-api/src/components/PokemonList.tsx @@ -7,7 +7,6 @@ const PokemonsQuery = graphql(` query Pokemons ($limit: Int = 10) { pokemons(limit: $limit) { id - f ...PokemonItem } } diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index 437549d8..8e289171 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -6,7 +6,12 @@ import { printSchema } from 'graphql'; import type { GraphQLSchema } from 'graphql'; import type { TsConfigJson } from 'type-fest'; -import { resolveTypeScriptRootDir, load, check } from '@gql.tada/internal'; +import { + resolveTypeScriptRootDir, + load, + check, + type FormattedDisplayableDiagnostic, +} from '@gql.tada/internal'; import type { GraphQLSPConfig } from './lsp'; import { getGraphQLSPConfig } from './lsp'; @@ -141,11 +146,56 @@ async function main() { if (!config) { return; } - check(config, opts.severity || 'error'); + const result = (await check(config, opts.severity || 'error')) || []; + const errorDiagnostics = result.filter((d) => d.severity === 'error'); + const warnDiagnostics = result.filter((d) => d.severity === 'warn'); + const infoDiagnostics = result.filter((d) => d.severity === 'info'); + if ( + errorDiagnostics.length === 0 && + warnDiagnostics.length === 0 && + infoDiagnostics.length === 0 + ) { + // eslint-disable-next-line no-console + console.log('No issues found! 🎉'); + return; + } else { + const errorReport = errorDiagnostics.length + ? `Found ${errorDiagnostics.length} Errors:\n\n${constructDiagnosticsPerFile( + errorDiagnostics + )}\n\n` + : ``; + const warningsReport = warnDiagnostics.length + ? `Found ${warnDiagnostics.length} Warnings:\n\n${constructDiagnosticsPerFile( + warnDiagnostics + )}\n\n` + : ``; + const suggestionsReport = infoDiagnostics.length + ? `Found ${infoDiagnostics.length} Suggestions:\n\n${constructDiagnosticsPerFile( + infoDiagnostics + )}\n\n` + : ``; + // eslint-disable-next-line no-console + console.log(`${errorReport}${warningsReport}${suggestionsReport}`); + } }); prog.parse(process.argv); } +function constructDiagnosticsPerFile(diagnostics: FormattedDisplayableDiagnostic[]): string { + const diagnosticsByFile = diagnostics.reduce>((acc, diag) => { + const file = diag.file || ''; + if (!acc[file]) { + acc[file] = []; + } + acc[file].push(`[${diag.line}:${diag.col}] ${diag.message}`); + return acc; + }, {}); + + return Object.entries(diagnosticsByFile).reduce((acc, [fileName, diagnostics]) => { + return `${acc}${fileName}\n${diagnostics.join('\n')}\n\n`; + }, ''); +} + const getConfig = async (): Promise => { const cwd = process.cwd(); const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts index aafc5766..a044aae8 100644 --- a/packages/internal/src/diagnostics/index.ts +++ b/packages/internal/src/diagnostics/index.ts @@ -6,9 +6,9 @@ import { resolveTypeScriptRootDir } from '../resolve'; import type { SchemaOrigin } from '../loaders'; import { load } from '../loaders'; -type Severity = 'error' | 'warning' | 'info'; -const severities: Severity[] = ['error', 'warning', 'info']; -interface FormattedDisplayableDiagnostic { +type Severity = 'error' | 'warn' | 'info'; +const severities: Severity[] = ['error', 'warn', 'info']; +export interface FormattedDisplayableDiagnostic { severity: Severity; message: string; line: number; @@ -103,7 +103,7 @@ export async function check( severity: (diag.category === ts.DiagnosticCategory.Error ? 'error' : diag.category === ts.DiagnosticCategory.Warning - ? 'warning' + ? 'warn' : 'info') as Severity, message: diag.messageText as string, file: diag.file && diag.file.fileName, @@ -126,7 +126,7 @@ function getLineCol(text: string, start: number): [number, number] { for (let i = 0; i <= parts.length; i++) { const line = parts[i]; if (counter + line.length > start) { - return [i + 1, start - counter]; + return [i + 1, start + 1 - counter]; } else { counter = counter + (line.length + 1); continue; diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index 1f57e7c2..9100d97d 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -8,4 +8,4 @@ export { } from './introspection'; export { resolveTypeScriptRootDir } from './resolve'; -export { check } from './diagnostics'; +export * from './diagnostics'; From 867621935b8152615be06d5b12d651ab8f303809 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Thu, 4 Apr 2024 21:06:16 +0200 Subject: [PATCH 12/20] changeset --- .changeset/nasty-ducks-hug.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/nasty-ducks-hug.md diff --git a/.changeset/nasty-ducks-hug.md b/.changeset/nasty-ducks-hug.md new file mode 100644 index 00000000..661b75ee --- /dev/null +++ b/.changeset/nasty-ducks-hug.md @@ -0,0 +1,6 @@ +--- +"@gql.tada/cli-utils": minor +"@gql.tada/internal": minor +--- + +Add `check` as a way to run the GraphQLSP diagnostics as part of our CLI From 71f2f9c45394268dbe465f00b38cc4b687eb084b Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:11:09 +0200 Subject: [PATCH 13/20] refactors --- packages/cli-utils/package.json | 2 + packages/cli-utils/src/commands/check.ts | 201 +++++++++++++++++++++ packages/cli-utils/src/index.ts | 109 ++--------- packages/cli-utils/src/tsconfig.ts | 32 ++++ packages/internal/package.json | 4 +- packages/internal/src/diagnostics/index.ts | 137 -------------- packages/internal/src/index.ts | 1 - pnpm-lock.yaml | 12 +- 8 files changed, 258 insertions(+), 240 deletions(-) create mode 100644 packages/cli-utils/src/commands/check.ts create mode 100644 packages/cli-utils/src/tsconfig.ts delete mode 100644 packages/internal/src/diagnostics/index.ts diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json index 8a22b0a3..7bd89508 100644 --- a/packages/cli-utils/package.json +++ b/packages/cli-utils/package.json @@ -49,6 +49,8 @@ }, "dependencies": { "@gql.tada/internal": "workspace:*", + "@0no-co/graphqlsp": "^1.8.0", + "ts-morph": "^22.0.0", "graphql": "^16.8.1" }, "peerDependencies": { diff --git a/packages/cli-utils/src/commands/check.ts b/packages/cli-utils/src/commands/check.ts new file mode 100644 index 00000000..163f5d97 --- /dev/null +++ b/packages/cli-utils/src/commands/check.ts @@ -0,0 +1,201 @@ +import { Project, ts } from 'ts-morph'; +import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; +import path from 'path'; +import fs from 'fs'; +import { getTsConfig } from '../tsconfig'; +import type { GraphQLSPConfig } from '../lsp'; +import { getGraphQLSPConfig } from '../lsp'; +import { load, resolveTypeScriptRootDir } from '@gql.tada/internal'; + +type Severity = 'error' | 'warn' | 'info'; +const severities: Severity[] = ['error', 'warn', 'info']; +export interface FormattedDisplayableDiagnostic { + severity: Severity; + message: string; + line: number; + col: number; + file: string | undefined; +} + +export type CheckOptions = { + /** + * Exit with a non-zero code if there are any warnings. + */ + exitOnWarn: boolean; + /** + * The minimum severity to report for. + */ + minSeverity: Severity; +}; + +export async function check(opts: CheckOptions) { + const tsConfig = await getTsConfig(); + if (!tsConfig) { + return; + } + + const config = getGraphQLSPConfig(tsConfig); + if (!config) { + return; + } + + const result = ((await runDiagnostics(config)) || []).filter( + (diag) => severities.indexOf(diag.severity) <= severities.indexOf(opts.minSeverity) + ); + const errorDiagnostics = result.filter((d) => d.severity === 'error'); + const warnDiagnostics = result.filter((d) => d.severity === 'warn'); + const infoDiagnostics = result.filter((d) => d.severity === 'info'); + + if ( + errorDiagnostics.length === 0 && + warnDiagnostics.length === 0 && + infoDiagnostics.length === 0 + ) { + // eslint-disable-next-line no-console + console.log('No issues found! 🎉'); + process.exit(0); + } else { + const errorReport = errorDiagnostics.length + ? `Found ${errorDiagnostics.length} Errors:\n\n${constructDiagnosticsPerFile( + errorDiagnostics + )}\n\n` + : ``; + const warningsReport = warnDiagnostics.length + ? `Found ${warnDiagnostics.length} Warnings:\n\n${constructDiagnosticsPerFile( + warnDiagnostics + )}\n\n` + : ``; + const suggestionsReport = infoDiagnostics.length + ? `Found ${infoDiagnostics.length} Suggestions:\n\n${constructDiagnosticsPerFile( + infoDiagnostics + )}\n\n` + : ``; + // eslint-disable-next-line no-console + console.log(`${errorReport}${warningsReport}${suggestionsReport}`); + if (errorDiagnostics.length || (opts.exitOnWarn && warnDiagnostics.length)) { + process.exit(1); + } else { + process.exit(0); + } + } +} + +async function runDiagnostics(config: GraphQLSPConfig): Promise { + const projectName = path.resolve(process.cwd(), 'tsconfig.json'); + const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); + const project = new Project({ + tsConfigFilePath: projectName, + }); + + init({ + typescript: ts as any, + }); + + const languageService = project.getLanguageService(); + const pluginCreateInfo = { + config, + languageService: { + getReferencesAtPosition: (filename, position) => { + return languageService.compilerObject.getReferencesAtPosition(filename, position); + }, + getDefinitionAtPosition: (filename, position) => { + return languageService.compilerObject.getDefinitionAtPosition(filename, position); + }, + getProgram: () => { + const program = project.getProgram(); + return { + ...program, + getTypeChecker: () => project.getTypeChecker(), + getSourceFile: (s) => { + const source = project.getSourceFile(s); + return source && source.compilerNode; + }, + }; + }, + // This prevents us from exposing normal diagnostics + getSemanticDiagnostics: () => [], + } as any, + languageServiceHost: {} as any, + project: { + getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), + readFile: (p) => { + return fs.readFileSync(p); + }, + projectService: { + logger: console, + }, + } as any, + serverHost: {} as any, + }; + + const sourceFiles = project.getSourceFiles(); + const loader = load({ origin: config.schema, rootPath }); + let schema; + try { + const loaderResult = await loader.load(); + schema = loaderResult && loaderResult.schema; + if (!schema) { + throw new Error(`Failed to load schema`); + } + } catch (error) { + throw new Error(`Failed to load schema: ${error}`); + } + + return sourceFiles.flatMap((sourceFile) => { + const diag = + getGraphQLDiagnostics( + sourceFile.getFilePath(), + { current: schema, version: 1 }, + pluginCreateInfo + ) || []; + return diag.map((diag) => { + const text = diag.file && diag.file.getText(); + const start = diag.start; + const [line, col] = getLineCol(text || '', start || 0); + return { + severity: (diag.category === ts.DiagnosticCategory.Error + ? 'error' + : diag.category === ts.DiagnosticCategory.Warning + ? 'warn' + : 'info') as Severity, + message: diag.messageText as string, + file: diag.file && diag.file.fileName, + line, + col, + }; + }); + }); +} + +function getLineCol(text: string, start: number): [number, number] { + if (!text || !start) return [0, 0]; + + let counter = 0; + const parts = text.split('\n'); + for (let i = 0; i <= parts.length; i++) { + const line = parts[i]; + if (counter + line.length > start) { + return [i + 1, start + 1 - counter]; + } else { + counter = counter + (line.length + 1); + continue; + } + } + + return [0, 0]; +} + +function constructDiagnosticsPerFile(diagnostics: FormattedDisplayableDiagnostic[]): string { + const diagnosticsByFile = diagnostics.reduce>((acc, diag) => { + const file = diag.file || ''; + if (!acc[file]) { + acc[file] = []; + } + acc[file].push(`[${diag.line}:${diag.col}] ${diag.message}`); + return acc; + }, {}); + + return Object.entries(diagnosticsByFile).reduce((acc, [fileName, diagnostics]) => { + return `${acc}${fileName}\n${diagnostics.join('\n')}\n\n`; + }, ''); +} diff --git a/packages/cli-utils/src/index.ts b/packages/cli-utils/src/index.ts index 8e289171..8438caba 100644 --- a/packages/cli-utils/src/index.ts +++ b/packages/cli-utils/src/index.ts @@ -6,16 +6,12 @@ import { printSchema } from 'graphql'; import type { GraphQLSchema } from 'graphql'; import type { TsConfigJson } from 'type-fest'; -import { - resolveTypeScriptRootDir, - load, - check, - type FormattedDisplayableDiagnostic, -} from '@gql.tada/internal'; - -import type { GraphQLSPConfig } from './lsp'; +import { load } from '@gql.tada/internal'; + import { getGraphQLSPConfig } from './lsp'; import { ensureTadaIntrospection } from './tada'; +import { getTsConfig } from './tsconfig'; +import { check } from './commands/check'; interface GenerateSchemaOptions { headers?: Record; @@ -78,7 +74,12 @@ export async function generateSchema( } export async function generateTadaTypes(shouldPreprocess = false, cwd: string = process.cwd()) { - const config = await getConfig(); + const tsConfig = await getTsConfig(); + if (!tsConfig) { + return; + } + + const config = getGraphQLSPConfig(tsConfig); if (!config) { return; } @@ -140,93 +141,15 @@ async function main() { return generateTadaTypes(shouldPreprocess); }) .command('check') - .option('--severity', 'The minimum severity of diagnostics to display.') + .option('--level', 'The minimum severity of diagnostics to display (error | warn | info).') + .option('--exit-on-warn', 'Whether to exit with a non-zero code when there are warnings.') .action(async (opts) => { - const config = await getConfig(); - if (!config) { - return; - } - const result = (await check(config, opts.severity || 'error')) || []; - const errorDiagnostics = result.filter((d) => d.severity === 'error'); - const warnDiagnostics = result.filter((d) => d.severity === 'warn'); - const infoDiagnostics = result.filter((d) => d.severity === 'info'); - if ( - errorDiagnostics.length === 0 && - warnDiagnostics.length === 0 && - infoDiagnostics.length === 0 - ) { - // eslint-disable-next-line no-console - console.log('No issues found! 🎉'); - return; - } else { - const errorReport = errorDiagnostics.length - ? `Found ${errorDiagnostics.length} Errors:\n\n${constructDiagnosticsPerFile( - errorDiagnostics - )}\n\n` - : ``; - const warningsReport = warnDiagnostics.length - ? `Found ${warnDiagnostics.length} Warnings:\n\n${constructDiagnosticsPerFile( - warnDiagnostics - )}\n\n` - : ``; - const suggestionsReport = infoDiagnostics.length - ? `Found ${infoDiagnostics.length} Suggestions:\n\n${constructDiagnosticsPerFile( - infoDiagnostics - )}\n\n` - : ``; - // eslint-disable-next-line no-console - console.log(`${errorReport}${warningsReport}${suggestionsReport}`); - } + check({ + exitOnWarn: opts['exit-on-warn'] !== undefined ? opts['exit-on-warn'] : false, + minSeverity: opts.level || 'error', + }); }); prog.parse(process.argv); } -function constructDiagnosticsPerFile(diagnostics: FormattedDisplayableDiagnostic[]): string { - const diagnosticsByFile = diagnostics.reduce>((acc, diag) => { - const file = diag.file || ''; - if (!acc[file]) { - acc[file] = []; - } - acc[file].push(`[${diag.line}:${diag.col}] ${diag.message}`); - return acc; - }, {}); - - return Object.entries(diagnosticsByFile).reduce((acc, [fileName, diagnostics]) => { - return `${acc}${fileName}\n${diagnostics.join('\n')}\n\n`; - }, ''); -} - -const getConfig = async (): Promise => { - const cwd = process.cwd(); - const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); - - // TODO: Remove redundant read and move tsconfig.json handling to internal package - const root = (await resolveTypeScriptRootDir(tsconfigpath)) || cwd; - - let tsconfigContents: string; - try { - const file = path.resolve(root, 'tsconfig.json'); - tsconfigContents = await fs.readFile(file, 'utf-8'); - } catch (error) { - console.error('Failed to read tsconfig.json in current working directory.', error); - return; - } - - let tsConfig: TsConfigJson; - try { - tsConfig = parse(tsconfigContents) as TsConfigJson; - } catch (err) { - console.error(err); - return; - } - - const config = getGraphQLSPConfig(tsConfig); - if (!config) { - console.error(`Could not find a "@0no-co/graphqlsp" plugin in your tsconfig.`); - return; - } - - return config; -}; - export default main; diff --git a/packages/cli-utils/src/tsconfig.ts b/packages/cli-utils/src/tsconfig.ts new file mode 100644 index 00000000..8bec5df5 --- /dev/null +++ b/packages/cli-utils/src/tsconfig.ts @@ -0,0 +1,32 @@ +import { resolveTypeScriptRootDir } from '@gql.tada/internal'; +import path from 'path'; +import type { TsConfigJson } from 'type-fest'; +import fs from 'node:fs/promises'; +import { parse } from 'json5'; + +export const getTsConfig = async (): Promise => { + const cwd = process.cwd(); + const tsconfigpath = path.resolve(cwd, 'tsconfig.json'); + + // TODO: Remove redundant read and move tsconfig.json handling to internal package + const root = (await resolveTypeScriptRootDir(tsconfigpath)) || cwd; + + let tsconfigContents: string; + try { + const file = path.resolve(root, 'tsconfig.json'); + tsconfigContents = await fs.readFile(file, 'utf-8'); + } catch (error) { + console.error('Failed to read tsconfig.json in current working directory.', error); + return; + } + + let tsConfig: TsConfigJson; + try { + tsConfig = parse(tsconfigContents) as TsConfigJson; + } catch (err) { + console.error(err); + return; + } + + return tsConfig; +}; diff --git a/packages/internal/package.json b/packages/internal/package.json index c6dd6aa7..5b03a742 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -53,9 +53,7 @@ "typescript": "^5.3.3" }, "dependencies": { - "@0no-co/graphql.web": "^1.0.5", - "@0no-co/graphqlsp": "^1.8.0", - "ts-morph": "^22.0.0" + "@0no-co/graphql.web": "^1.0.5" }, "peerDependencies": { "typescript": "^5.0.0", diff --git a/packages/internal/src/diagnostics/index.ts b/packages/internal/src/diagnostics/index.ts deleted file mode 100644 index a044aae8..00000000 --- a/packages/internal/src/diagnostics/index.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Project, ts } from 'ts-morph'; -import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; -import path from 'path'; -import fs from 'fs'; -import { resolveTypeScriptRootDir } from '../resolve'; -import type { SchemaOrigin } from '../loaders'; -import { load } from '../loaders'; - -type Severity = 'error' | 'warn' | 'info'; -const severities: Severity[] = ['error', 'warn', 'info']; -export interface FormattedDisplayableDiagnostic { - severity: Severity; - message: string; - line: number; - col: number; - file: string | undefined; -} - -type GraphQLSPConfig = { - name: string; - schema: SchemaOrigin; - tadaOutputLocation: string; -}; - -export async function check( - config: GraphQLSPConfig, - minSeverity: Severity = 'error' -): Promise { - const projectName = path.resolve(process.cwd(), 'tsconfig.json'); - - const project = new Project({ - tsConfigFilePath: './tsconfig.json', - }); - - const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); - init({ - typescript: ts as any, - }); - - const languageService = project.getLanguageService(); - const pluginCreateInfo = { - config, - languageService: { - getReferencesAtPosition: (filename, position) => { - return languageService.compilerObject.getReferencesAtPosition(filename, position); - }, - getDefinitionAtPosition: (filename, position) => { - return languageService.compilerObject.getDefinitionAtPosition(filename, position); - }, - getProgram: () => { - const program = project.getProgram(); - return { - ...program, - getTypeChecker: () => project.getTypeChecker(), - getSourceFile: (s) => { - const source = project.getSourceFile(s); - return source && source.compilerNode; - }, - }; - }, - // This prevents us from exposing normal diagnostics - getSemanticDiagnostics: () => [], - } as any, - languageServiceHost: {} as any, - project: { - getProjectName: () => path.resolve(process.cwd(), 'tsconfig.json'), - readFile: (p) => { - return fs.readFileSync(p); - }, - projectService: { - logger: console, - }, - } as any, - serverHost: {} as any, - }; - - const sourceFiles = project.getSourceFiles(); - const loader = load({ origin: config.schema, rootPath }); - let schema; - try { - const loaderResult = await loader.load(); - schema = loaderResult && loaderResult.schema; - if (!schema) { - throw new Error(`Failed to load schema`); - } - } catch (error) { - throw new Error(`Failed to load schema: ${error}`); - } - - const allDiagnostics: FormattedDisplayableDiagnostic[] = sourceFiles - .flatMap((sourceFile) => { - const diag = - getGraphQLDiagnostics( - sourceFile.getFilePath(), - { current: schema, version: 1 }, - pluginCreateInfo - ) || []; - return diag.map((diag) => { - const text = diag.file && diag.file.getText(); - const start = diag.start; - const [line, col] = getLineCol(text || '', start || 0); - return { - severity: (diag.category === ts.DiagnosticCategory.Error - ? 'error' - : diag.category === ts.DiagnosticCategory.Warning - ? 'warn' - : 'info') as Severity, - message: diag.messageText as string, - file: diag.file && diag.file.fileName, - line, - col, - }; - }); - }) - // Filter out diagnostics below the minimum severity - .filter((diag) => severities.indexOf(diag.severity) <= severities.indexOf(minSeverity)); - - return allDiagnostics; -} - -function getLineCol(text: string, start: number): [number, number] { - if (!text || !start) return [0, 0]; - - let counter = 0; - const parts = text.split('\n'); - for (let i = 0; i <= parts.length; i++) { - const line = parts[i]; - if (counter + line.length > start) { - return [i + 1, start + 1 - counter]; - } else { - counter = counter + (line.length + 1); - continue; - } - } - - return [0, 0]; -} diff --git a/packages/internal/src/index.ts b/packages/internal/src/index.ts index 9100d97d..4a5dbc44 100644 --- a/packages/internal/src/index.ts +++ b/packages/internal/src/index.ts @@ -8,4 +8,3 @@ export { } from './introspection'; export { resolveTypeScriptRootDir } from './resolve'; -export * from './diagnostics'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bb54de6..945ad1c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,12 +153,18 @@ importers: packages/cli-utils: dependencies: + '@0no-co/graphqlsp': + specifier: ^1.8.0 + version: 1.8.0(typescript@5.4.2) '@gql.tada/internal': specifier: workspace:* version: link:../internal graphql: specifier: ^16.8.1 version: 16.8.1 + ts-morph: + specifier: ^22.0.0 + version: 22.0.0 devDependencies: '@types/node': specifier: ^20.11.0 @@ -184,12 +190,6 @@ importers: '@0no-co/graphql.web': specifier: ^1.0.5 version: 1.0.5(graphql@16.8.1) - '@0no-co/graphqlsp': - specifier: ^1.8.0 - version: 1.8.0(typescript@5.4.2) - ts-morph: - specifier: ^22.0.0 - version: 22.0.0 devDependencies: '@types/node': specifier: ^20.11.0 From 7857d974aaf9c076dbf406983cfc321ba8b8c638 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:15:41 +0200 Subject: [PATCH 14/20] prettier formatting --- .../src/components/PokemonItem.tsx | 1 + packages/cli-utils/src/commands/check.ts | 14 +++++--------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/example-pokemon-api/src/components/PokemonItem.tsx b/examples/example-pokemon-api/src/components/PokemonItem.tsx index 705e2e7c..6b706ba9 100644 --- a/examples/example-pokemon-api/src/components/PokemonItem.tsx +++ b/examples/example-pokemon-api/src/components/PokemonItem.tsx @@ -4,6 +4,7 @@ export const PokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name + f } `); diff --git a/packages/cli-utils/src/commands/check.ts b/packages/cli-utils/src/commands/check.ts index 163f5d97..ee1101ff 100644 --- a/packages/cli-utils/src/commands/check.ts +++ b/packages/cli-utils/src/commands/check.ts @@ -56,19 +56,15 @@ export async function check(opts: CheckOptions) { process.exit(0); } else { const errorReport = errorDiagnostics.length - ? `Found ${errorDiagnostics.length} Errors:\n\n${constructDiagnosticsPerFile( - errorDiagnostics - )}\n\n` + ? `Found ${errorDiagnostics.length} Errors:\n${constructDiagnosticsPerFile(errorDiagnostics)}` : ``; const warningsReport = warnDiagnostics.length - ? `Found ${warnDiagnostics.length} Warnings:\n\n${constructDiagnosticsPerFile( - warnDiagnostics - )}\n\n` + ? `Found ${warnDiagnostics.length} Warnings:\n${constructDiagnosticsPerFile(warnDiagnostics)}` : ``; const suggestionsReport = infoDiagnostics.length - ? `Found ${infoDiagnostics.length} Suggestions:\n\n${constructDiagnosticsPerFile( + ? `Found ${infoDiagnostics.length} Suggestions:\n${constructDiagnosticsPerFile( infoDiagnostics - )}\n\n` + )}` : ``; // eslint-disable-next-line no-console console.log(`${errorReport}${warningsReport}${suggestionsReport}`); @@ -196,6 +192,6 @@ function constructDiagnosticsPerFile(diagnostics: FormattedDisplayableDiagnostic }, {}); return Object.entries(diagnosticsByFile).reduce((acc, [fileName, diagnostics]) => { - return `${acc}${fileName}\n${diagnostics.join('\n')}\n\n`; + return `${acc}\n${fileName}\n${diagnostics.join('\n')}\n`; }, ''); } From 6520254d77842d42a4162d9487a635fa2be1c119 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:17:14 +0200 Subject: [PATCH 15/20] more fixes --- packages/cli-utils/src/commands/check.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/cli-utils/src/commands/check.ts b/packages/cli-utils/src/commands/check.ts index ee1101ff..8dd6b402 100644 --- a/packages/cli-utils/src/commands/check.ts +++ b/packages/cli-utils/src/commands/check.ts @@ -1,7 +1,6 @@ import { Project, ts } from 'ts-morph'; import { init, getGraphQLDiagnostics } from '@0no-co/graphqlsp/api'; import path from 'path'; -import fs from 'fs'; import { getTsConfig } from '../tsconfig'; import type { GraphQLSPConfig } from '../lsp'; import { getGraphQLSPConfig } from '../lsp'; @@ -114,9 +113,6 @@ async function runDiagnostics(config: GraphQLSPConfig): Promise path.resolve(process.cwd(), 'tsconfig.json'), - readFile: (p) => { - return fs.readFileSync(p); - }, projectService: { logger: console, }, From 76d2de755fc2af9cf54dc1e14b1b5a2b28af5464 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:19:56 +0200 Subject: [PATCH 16/20] tweaks --- packages/cli-utils/src/commands/check.ts | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/cli-utils/src/commands/check.ts b/packages/cli-utils/src/commands/check.ts index 8dd6b402..3f31128e 100644 --- a/packages/cli-utils/src/commands/check.ts +++ b/packages/cli-utils/src/commands/check.ts @@ -38,13 +38,12 @@ export async function check(opts: CheckOptions) { return; } - const result = ((await runDiagnostics(config)) || []).filter( - (diag) => severities.indexOf(diag.severity) <= severities.indexOf(opts.minSeverity) - ); + const result = (await runDiagnostics(config)) || []; const errorDiagnostics = result.filter((d) => d.severity === 'error'); const warnDiagnostics = result.filter((d) => d.severity === 'warn'); const infoDiagnostics = result.filter((d) => d.severity === 'info'); + const minSeverityForReport = severities.indexOf(opts.minSeverity); if ( errorDiagnostics.length === 0 && warnDiagnostics.length === 0 && @@ -57,14 +56,18 @@ export async function check(opts: CheckOptions) { const errorReport = errorDiagnostics.length ? `Found ${errorDiagnostics.length} Errors:\n${constructDiagnosticsPerFile(errorDiagnostics)}` : ``; - const warningsReport = warnDiagnostics.length - ? `Found ${warnDiagnostics.length} Warnings:\n${constructDiagnosticsPerFile(warnDiagnostics)}` - : ``; - const suggestionsReport = infoDiagnostics.length - ? `Found ${infoDiagnostics.length} Suggestions:\n${constructDiagnosticsPerFile( - infoDiagnostics - )}` - : ``; + const warningsReport = + minSeverityForReport >= severities.indexOf('warn') && warnDiagnostics.length + ? `Found ${warnDiagnostics.length} Warnings:\n${constructDiagnosticsPerFile( + warnDiagnostics + )}` + : ``; + const suggestionsReport = + minSeverityForReport >= severities.indexOf('info') && infoDiagnostics.length + ? `Found ${infoDiagnostics.length} Suggestions:\n${constructDiagnosticsPerFile( + infoDiagnostics + )}` + : ``; // eslint-disable-next-line no-console console.log(`${errorReport}${warningsReport}${suggestionsReport}`); if (errorDiagnostics.length || (opts.exitOnWarn && warnDiagnostics.length)) { From df4bb5e86ad3a00b154a2e8299cac2ce1488d9bd Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:22:14 +0200 Subject: [PATCH 17/20] add todos --- packages/cli-utils/src/commands/check.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/cli-utils/src/commands/check.ts b/packages/cli-utils/src/commands/check.ts index 3f31128e..c3313a9a 100644 --- a/packages/cli-utils/src/commands/check.ts +++ b/packages/cli-utils/src/commands/check.ts @@ -53,6 +53,7 @@ export async function check(opts: CheckOptions) { console.log('No issues found! 🎉'); process.exit(0); } else { + // TODO: report a summary at the top and then a list of files with diagnostics sorted by severity. const errorReport = errorDiagnostics.length ? `Found ${errorDiagnostics.length} Errors:\n${constructDiagnosticsPerFile(errorDiagnostics)}` : ``; @@ -63,7 +64,10 @@ export async function check(opts: CheckOptions) { )}` : ``; const suggestionsReport = - minSeverityForReport >= severities.indexOf('info') && infoDiagnostics.length + minSeverityForReport >= severities.indexOf('info') && + infoDiagnostics.length && + warnDiagnostics.length && + errorDiagnostics.length ? `Found ${infoDiagnostics.length} Suggestions:\n${constructDiagnosticsPerFile( infoDiagnostics )}` @@ -79,6 +83,7 @@ export async function check(opts: CheckOptions) { } async function runDiagnostics(config: GraphQLSPConfig): Promise { + // TODO: leverage ts-morph tsconfig resolver const projectName = path.resolve(process.cwd(), 'tsconfig.json'); const rootPath = (await resolveTypeScriptRootDir(projectName)) || path.dirname(projectName); const project = new Project({ From 7aa24eb9ae9063707d15514ea723347719b912ee Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 08:22:34 +0200 Subject: [PATCH 18/20] remove faulty selection --- examples/example-pokemon-api/src/components/PokemonItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/example-pokemon-api/src/components/PokemonItem.tsx b/examples/example-pokemon-api/src/components/PokemonItem.tsx index 6b706ba9..705e2e7c 100644 --- a/examples/example-pokemon-api/src/components/PokemonItem.tsx +++ b/examples/example-pokemon-api/src/components/PokemonItem.tsx @@ -4,7 +4,6 @@ export const PokemonItemFragment = graphql(` fragment PokemonItem on Pokemon { id name - f } `); From 1222c2a9bae23184453d55e2be8d6070f95250ff Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 12:57:34 +0200 Subject: [PATCH 19/20] fixies --- examples/example-pokemon-api/package.json | 2 +- packages/internal/package.json | 1 + packages/internal/src/resolve.ts | 1 + pnpm-lock.yaml | 24 ++++++++--------------- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/examples/example-pokemon-api/package.json b/examples/example-pokemon-api/package.json index e0dac007..62e1f4ff 100644 --- a/examples/example-pokemon-api/package.json +++ b/examples/example-pokemon-api/package.json @@ -14,7 +14,7 @@ "urql": "^4.0.6" }, "devDependencies": { - "@0no-co/graphqlsp": "^1.5.0", + "@0no-co/graphqlsp": "^1.8.0", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", "@vitejs/plugin-react": "^4.2.1", diff --git a/packages/internal/package.json b/packages/internal/package.json index 5b03a742..9751a8fb 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -53,6 +53,7 @@ "typescript": "^5.3.3" }, "dependencies": { + "@ts-morph/common": "^0.23.0", "@0no-co/graphql.web": "^1.0.5" }, "peerDependencies": { diff --git a/packages/internal/src/resolve.ts b/packages/internal/src/resolve.ts index 75b606ac..b832fd3a 100644 --- a/packages/internal/src/resolve.ts +++ b/packages/internal/src/resolve.ts @@ -2,6 +2,7 @@ import path from 'path'; import JSON5 from 'json5'; import fs from 'node:fs/promises'; import type { TsConfigJson } from 'type-fest'; +import { TsConfigResolver } from '@ts-morph/common'; // TODO: Replace config loading with typescript package's native config loading export const resolveTypeScriptRootDir = async ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 945ad1c5..6dd9f1f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,8 +133,8 @@ importers: version: 4.0.6(graphql@16.8.1)(react@18.2.0) devDependencies: '@0no-co/graphqlsp': - specifier: ^1.5.0 - version: 1.5.0 + specifier: ^1.8.0 + version: 1.8.0(typescript@5.4.2) '@types/react': specifier: ^18.2.64 version: 18.2.64 @@ -190,6 +190,9 @@ importers: '@0no-co/graphql.web': specifier: ^1.0.5 version: 1.0.5(graphql@16.8.1) + '@ts-morph/common': + specifier: ^0.23.0 + version: 0.23.0 devDependencies: '@types/node': specifier: ^20.11.0 @@ -283,27 +286,17 @@ packages: dependencies: graphql: 16.8.1 - /@0no-co/graphqlsp@1.5.0: - resolution: {integrity: sha512-NGMUwzj7m6DfIqudZxfTfUEmzKR2n9xphPssCKqkGsMEKe9qIzrNqLpXXwzXTWrGBS5FBELW+TXGUyTa0v1LAw==} - dependencies: - json5: 2.2.3 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - dev: true - /@0no-co/graphqlsp@1.8.0(typescript@5.4.2): resolution: {integrity: sha512-DVET7krt1doddpSDg97t41mZZru2P/CWv22JXG4N1oFo1A1KFVGdka/chnn4pmB7/FqRCRUJARGvZNwwb1EJjw==} peerDependencies: typescript: ^5.3.3 dependencies: - '@gql.tada/internal': 0.1.2(graphql@16.8.1)(typescript@5.4.2) + '@gql.tada/internal': 0.1.3(graphql@16.8.1)(typescript@5.4.2) graphql: 16.8.1 node-fetch: 2.7.0 typescript: 5.4.2 transitivePeerDependencies: - encoding - dev: false /@0no-co/typescript.js@5.3.2-2: resolution: {integrity: sha512-IGQZZ7vcVD/GOUKLJckpQiq8F5raQZLR7kOVhxN5nHOywl1dB9EFMA7FJkyNoCEig7EkCb291X8FswMwwrz9yg==} @@ -1456,8 +1449,8 @@ packages: resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} dev: true - /@gql.tada/internal@0.1.2(graphql@16.8.1)(typescript@5.4.2): - resolution: {integrity: sha512-8I4Z1zxYYGK66FWdB3yIZBn3cITLPnciEgjChp3K2+Ha1e/AEBGtZv9AUlodraO/RZafDMkpFhoi+tMpluBjeg==} + /@gql.tada/internal@0.1.3(graphql@16.8.1)(typescript@5.4.2): + resolution: {integrity: sha512-wIvykBId7O0gaizmSl5n5AhbQsgJVLTUsFBm3RsfQ9dVfpmT+Fhy2yHX+yNgiVECg2EimXMhs4ltcE4EuZ2WOA==} peerDependencies: graphql: ^16.8.1 typescript: ^5.3.3 @@ -1465,7 +1458,6 @@ packages: '@0no-co/graphql.web': 1.0.5(graphql@16.8.1) graphql: 16.8.1 typescript: 5.4.2 - dev: false /@graphql-typed-document-node/core@3.2.0(graphql@16.8.1): resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} From 105550e5f19cf9d5a7e525e443271461bf2b80eb Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 5 Apr 2024 12:58:15 +0200 Subject: [PATCH 20/20] ts-morph --- examples/example-pokemon-api/package.json | 1 - .../example-pokemon-api/src/graphql-env.d.ts | 433 +----------------- packages/cli-utils/package.json | 2 +- packages/internal/package.json | 1 - packages/internal/src/resolve.ts | 1 - pnpm-lock.yaml | 8 +- 6 files changed, 19 insertions(+), 427 deletions(-) diff --git a/examples/example-pokemon-api/package.json b/examples/example-pokemon-api/package.json index 62e1f4ff..b32b89b8 100644 --- a/examples/example-pokemon-api/package.json +++ b/examples/example-pokemon-api/package.json @@ -10,7 +10,6 @@ "gql.tada": "^1.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "ts-morph": "^22.0.0", "urql": "^4.0.6" }, "devDependencies": { diff --git a/examples/example-pokemon-api/src/graphql-env.d.ts b/examples/example-pokemon-api/src/graphql-env.d.ts index 8d524386..59df4aaa 100644 --- a/examples/example-pokemon-api/src/graphql-env.d.ts +++ b/examples/example-pokemon-api/src/graphql-env.d.ts @@ -10,422 +10,23 @@ * instead save to a .ts instead of a .d.ts file. */ export type introspection = { - "__schema": { - "queryType": { - "name": "Query" - }, - "mutationType": null, - "subscriptionType": null, - "types": [ - { - "kind": "OBJECT", - "name": "Attack", - "fields": [ - { - "name": "damage", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "args": [] - }, - { - "name": "name", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "args": [] - }, - { - "name": "type", - "type": { - "kind": "ENUM", - "name": "PokemonType", - "ofType": null - }, - "args": [] - } - ], - "interfaces": [] - }, - { - "kind": "SCALAR", - "name": "Int" - }, - { - "kind": "SCALAR", - "name": "String" - }, - { - "kind": "OBJECT", - "name": "AttacksConnection", - "fields": [ - { - "name": "fast", - "type": { - "kind": "LIST", - "ofType": { - "kind": "OBJECT", - "name": "Attack", - "ofType": null - } - }, - "args": [] - }, - { - "name": "special", - "type": { - "kind": "LIST", - "ofType": { - "kind": "OBJECT", - "name": "Attack", - "ofType": null - } - }, - "args": [] - } - ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "EvolutionRequirement", - "fields": [ - { - "name": "amount", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "args": [] - }, - { - "name": "name", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "args": [] - } - ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "Pokemon", - "fields": [ - { - "name": "attacks", - "type": { - "kind": "OBJECT", - "name": "AttacksConnection", - "ofType": null - }, - "args": [] - }, - { - "name": "classification", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "args": [] - }, - { - "name": "evolutionRequirements", - "type": { - "kind": "LIST", - "ofType": { - "kind": "OBJECT", - "name": "EvolutionRequirement", - "ofType": null - } - }, - "args": [] - }, - { - "name": "evolutions", - "type": { - "kind": "LIST", - "ofType": { - "kind": "OBJECT", - "name": "Pokemon", - "ofType": null - } - }, - "args": [] - }, - { - "name": "fleeRate", - "type": { - "kind": "SCALAR", - "name": "Float", - "ofType": null - }, - "args": [] - }, - { - "name": "height", - "type": { - "kind": "OBJECT", - "name": "PokemonDimension", - "ofType": null - }, - "args": [] - }, - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - }, - "args": [] - }, - { - "name": "maxCP", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "args": [] - }, - { - "name": "maxHP", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - }, - "args": [] - }, - { - "name": "name", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } - }, - "args": [] - }, - { - "name": "resistant", - "type": { - "kind": "LIST", - "ofType": { - "kind": "ENUM", - "name": "PokemonType", - "ofType": null - } - }, - "args": [] - }, - { - "name": "types", - "type": { - "kind": "LIST", - "ofType": { - "kind": "ENUM", - "name": "PokemonType", - "ofType": null - } - }, - "args": [] - }, - { - "name": "weaknesses", - "type": { - "kind": "LIST", - "ofType": { - "kind": "ENUM", - "name": "PokemonType", - "ofType": null - } - }, - "args": [] - }, - { - "name": "weight", - "type": { - "kind": "OBJECT", - "name": "PokemonDimension", - "ofType": null - }, - "args": [] - } - ], - "interfaces": [] - }, - { - "kind": "SCALAR", - "name": "Float" - }, - { - "kind": "SCALAR", - "name": "ID" - }, - { - "kind": "OBJECT", - "name": "PokemonDimension", - "fields": [ - { - "name": "maximum", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "args": [] - }, - { - "name": "minimum", - "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null - }, - "args": [] - } - ], - "interfaces": [] - }, - { - "kind": "ENUM", - "name": "PokemonType", - "enumValues": [ - { - "name": "Bug" - }, - { - "name": "Dark" - }, - { - "name": "Dragon" - }, - { - "name": "Electric" - }, - { - "name": "Fairy" - }, - { - "name": "Fighting" - }, - { - "name": "Fire" - }, - { - "name": "Flying" - }, - { - "name": "Ghost" - }, - { - "name": "Grass" - }, - { - "name": "Ground" - }, - { - "name": "Ice" - }, - { - "name": "Normal" - }, - { - "name": "Poison" - }, - { - "name": "Psychic" - }, - { - "name": "Rock" - }, - { - "name": "Steel" - }, - { - "name": "Water" - } - ] - }, - { - "kind": "OBJECT", - "name": "Query", - "fields": [ - { - "name": "pokemon", - "type": { - "kind": "OBJECT", - "name": "Pokemon", - "ofType": null - }, - "args": [ - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "ID", - "ofType": null - } - } - } - ] - }, - { - "name": "pokemons", - "type": { - "kind": "LIST", - "ofType": { - "kind": "OBJECT", - "name": "Pokemon", - "ofType": null - } - }, - "args": [ - { - "name": "limit", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - }, - { - "name": "skip", - "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } - } - ] - } - ], - "interfaces": [] - }, - { - "kind": "SCALAR", - "name": "Boolean" - } - ], - "directives": [] - } + query: 'Query'; + mutation: never; + subscription: never; + types: { + 'Attack': { kind: 'OBJECT'; name: 'Attack'; fields: { 'damage': { name: 'damage'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; } }; }; }; + 'Int': unknown; + 'String': unknown; + 'AttacksConnection': { kind: 'OBJECT'; name: 'AttacksConnection'; fields: { 'fast': { name: 'fast'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; 'special': { name: 'special'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Attack'; ofType: null; }; } }; }; }; + 'EvolutionRequirement': { kind: 'OBJECT'; name: 'EvolutionRequirement'; fields: { 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'Pokemon': { kind: 'OBJECT'; name: 'Pokemon'; fields: { 'attacks': { name: 'attacks'; type: { kind: 'OBJECT'; name: 'AttacksConnection'; ofType: null; } }; 'classification': { name: 'classification'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'evolutionRequirements': { name: 'evolutionRequirements'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'EvolutionRequirement'; ofType: null; }; } }; 'evolutions': { name: 'evolutions'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; 'fleeRate': { name: 'fleeRate'; type: { kind: 'SCALAR'; name: 'Float'; ofType: null; } }; 'height': { name: 'height'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'maxCP': { name: 'maxCP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxHP': { name: 'maxHP'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'resistant': { name: 'resistant'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'types': { name: 'types'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weaknesses': { name: 'weaknesses'; type: { kind: 'LIST'; name: never; ofType: { kind: 'ENUM'; name: 'PokemonType'; ofType: null; }; } }; 'weight': { name: 'weight'; type: { kind: 'OBJECT'; name: 'PokemonDimension'; ofType: null; } }; }; }; + 'Float': unknown; + 'ID': unknown; + 'PokemonDimension': { kind: 'OBJECT'; name: 'PokemonDimension'; fields: { 'maximum': { name: 'maximum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'minimum': { name: 'minimum'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; + 'PokemonType': { kind: 'ENUM'; name: 'PokemonType'; type: 'Bug' | 'Dark' | 'Dragon' | 'Electric' | 'Fairy' | 'Fighting' | 'Fire' | 'Flying' | 'Ghost' | 'Grass' | 'Ground' | 'Ice' | 'Normal' | 'Poison' | 'Psychic' | 'Rock' | 'Steel' | 'Water'; }; + 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'pokemon': { name: 'pokemon'; type: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; } }; 'pokemons': { name: 'pokemons'; type: { kind: 'LIST'; name: never; ofType: { kind: 'OBJECT'; name: 'Pokemon'; ofType: null; }; } }; }; }; + 'Boolean': unknown; + }; }; import * as gqlTada from 'gql.tada'; diff --git a/packages/cli-utils/package.json b/packages/cli-utils/package.json index 7bd89508..2d053919 100644 --- a/packages/cli-utils/package.json +++ b/packages/cli-utils/package.json @@ -50,7 +50,7 @@ "dependencies": { "@gql.tada/internal": "workspace:*", "@0no-co/graphqlsp": "^1.8.0", - "ts-morph": "^22.0.0", + "ts-morph": "~22.0.0", "graphql": "^16.8.1" }, "peerDependencies": { diff --git a/packages/internal/package.json b/packages/internal/package.json index 9751a8fb..5b03a742 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -53,7 +53,6 @@ "typescript": "^5.3.3" }, "dependencies": { - "@ts-morph/common": "^0.23.0", "@0no-co/graphql.web": "^1.0.5" }, "peerDependencies": { diff --git a/packages/internal/src/resolve.ts b/packages/internal/src/resolve.ts index b832fd3a..75b606ac 100644 --- a/packages/internal/src/resolve.ts +++ b/packages/internal/src/resolve.ts @@ -2,7 +2,6 @@ import path from 'path'; import JSON5 from 'json5'; import fs from 'node:fs/promises'; import type { TsConfigJson } from 'type-fest'; -import { TsConfigResolver } from '@ts-morph/common'; // TODO: Replace config loading with typescript package's native config loading export const resolveTypeScriptRootDir = async ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dd9f1f0..18315e92 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,9 +125,6 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - ts-morph: - specifier: ^22.0.0 - version: 22.0.0 urql: specifier: ^4.0.6 version: 4.0.6(graphql@16.8.1)(react@18.2.0) @@ -163,7 +160,7 @@ importers: specifier: ^16.8.1 version: 16.8.1 ts-morph: - specifier: ^22.0.0 + specifier: ~22.0.0 version: 22.0.0 devDependencies: '@types/node': @@ -190,9 +187,6 @@ importers: '@0no-co/graphql.web': specifier: ^1.0.5 version: 1.0.5(graphql@16.8.1) - '@ts-morph/common': - specifier: ^0.23.0 - version: 0.23.0 devDependencies: '@types/node': specifier: ^20.11.0