diff --git a/packages/vscode-graphql-execution/src/extension.ts b/packages/vscode-graphql-execution/src/extension.ts index 4825f5738a7..51d28c92761 100644 --- a/packages/vscode-graphql-execution/src/extension.ts +++ b/packages/vscode-graphql-execution/src/extension.ts @@ -42,30 +42,31 @@ export function activate(context: ExtensionContext) { // const settings = workspace.getConfiguration("vscode-graphql-execution") // let provider: GraphQLCodeLensProvider; const registerCodeLens = () => { - context.subscriptions.push( - languages.registerCodeLensProvider( - [ - 'javascript', - 'typescript', - 'javascriptreact', - 'typescriptreact', - 'graphql', - ], - new GraphQLCodeLensProvider(outputChannel), - ), + const provider = languages.registerCodeLensProvider( + [ + 'javascript', + 'typescript', + 'javascriptreact', + 'typescriptreact', + 'graphql', + ], + new GraphQLCodeLensProvider(outputChannel), ); + context.subscriptions.push(provider); + return provider; }; // if (settings.showExecCodelens !== false) { - registerCodeLens(); + const codeLensProvider = registerCodeLens(); + // } let commandContentProvider: GraphQLContentProvider; const registerContentProvider = () => { - return commands.registerCommand( + const provider = commands.registerCommand( 'vscode-graphql-execution.contentProvider', - (literal: ExtractedTemplateLiteral) => { + async (literal: ExtractedTemplateLiteral) => { const uri = Uri.parse('graphql://authority/graphql'); const panel = window.createWebviewPanel( @@ -81,6 +82,7 @@ export function activate(context: ExtensionContext) { literal, panel, ); + await commandContentProvider.loadProvider(); const registration = workspace.registerTextDocumentContentProvider( 'graphql', commandContentProvider, @@ -89,23 +91,33 @@ export function activate(context: ExtensionContext) { panel.webview.html = commandContentProvider.getCurrentHtml(); }, ); + context.subscriptions.push(provider); + return provider; }; - const provider = registerContentProvider(); - context.subscriptions.push(provider); + const contentProvider = registerContentProvider(); // workspace.onDidChangeConfiguration(async () => { // // const newSettings = workspace.getConfiguration("vscode-graphql-execution") // // if (newSettings.showExecCodeLens !== false) { - // commandContentProvider.dispose() + // provider.dispose(); // // } // }); + workspace.onDidSaveTextDocument(async e => { if ( e.fileName.includes('graphql.config') || e.fileName.includes('graphqlrc') ) { - await commandContentProvider.loadConfig(); + if (contentProvider) { + await contentProvider.dispose(); + registerContentProvider(); + } + + if (codeLensProvider) { + await codeLensProvider.dispose(); + registerCodeLens(); + } } }); } diff --git a/packages/vscode-graphql-execution/src/helpers/source.ts b/packages/vscode-graphql-execution/src/helpers/source.ts index cc7d6564d6d..34eaf40bdf3 100644 --- a/packages/vscode-graphql-execution/src/helpers/source.ts +++ b/packages/vscode-graphql-execution/src/helpers/source.ts @@ -172,11 +172,36 @@ export class SourceHelper { } catch {} } + const regExpInline = new RegExp( + '`[\\n\\r\\s]*#graphql+([\\s\\S]+?)`', + 'mg', + ); + + let inlineResult: RegExpExecArray | null; + + while ((inlineResult = regExpInline.exec(text)) !== null) { + const contents = inlineResult[1]; + + // https://regex101.com/r/KFMXFg/2 + if (contents.match('/${(.+)?}/g')) { + // We are ignoring operations with template variables for now + continue; + } + try { + processGraphQLString(contents, inlineResult.index + 1); + + // no-op on exception, so that non-parse-able source files + // don't break the extension while editing + } catch {} + } + for (const tag of tags) { // https://regex101.com/r/Pd5PaU/2 const regExpGQL = new RegExp(tag + '\\s*`([\\s\\S]+?)`', 'mg'); + // https://regex101.com/r/FvG9qc/1 let result: RegExpExecArray | null; + while ((result = regExpGQL.exec(text)) !== null) { const contents = result[1]; @@ -199,6 +224,7 @@ export class SourceHelper { const operations = ast.definitions.filter( def => def.kind === 'OperationDefinition', ); + for (const operation of operations) { const op = operation as any; const filteredAst = { diff --git a/packages/vscode-graphql-execution/src/providers/exec-codelens.ts b/packages/vscode-graphql-execution/src/providers/exec-codelens.ts index d97bbb56881..dd3346b455d 100644 --- a/packages/vscode-graphql-execution/src/providers/exec-codelens.ts +++ b/packages/vscode-graphql-execution/src/providers/exec-codelens.ts @@ -6,27 +6,41 @@ import { CodeLens, Range, Position, - ProviderResult, } from 'vscode'; import { SourceHelper, ExtractedTemplateLiteral } from '../helpers/source'; import capitalize from 'capitalize'; +import { GraphQLContentProvider } from './exec-content'; export class GraphQLCodeLensProvider implements CodeLensProvider { outputChannel: OutputChannel; sourceHelper: SourceHelper; + contentProvider?: GraphQLContentProvider; constructor(outputChannel: OutputChannel) { this.outputChannel = outputChannel; this.sourceHelper = new SourceHelper(this.outputChannel); } - public provideCodeLenses( + public async provideCodeLenses( document: TextDocument, _token: CancellationToken, // for some reason, ProviderResult doesn't work here // anymore after upgrading types - ): ProviderResult<[]> { + ): Promise { + this.contentProvider = new GraphQLContentProvider( + document.uri, + this.outputChannel, + // @ts-expect-error + { uri: document.uri.fsPath }, + ); + await this.contentProvider.loadConfig(); + if ( + !this.contentProvider.hasConfig || + !(await this.contentProvider.loadEndpoint()) + ) { + return []; + } const literals: ExtractedTemplateLiteral[] = this.sourceHelper.extractAllTemplateLiterals(document, [ 'gql', @@ -47,6 +61,6 @@ export class GraphQLCodeLensProvider implements CodeLensProvider { ); }); - return results as ProviderResult<[]>; + return results; } } diff --git a/packages/vscode-graphql-execution/src/providers/exec-content.ts b/packages/vscode-graphql-execution/src/providers/exec-content.ts index 44015a53787..5d02c857136 100644 --- a/packages/vscode-graphql-execution/src/providers/exec-content.ts +++ b/packages/vscode-graphql-execution/src/providers/exec-content.ts @@ -31,7 +31,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { private outputChannel: OutputChannel; private networkHelper: NetworkHelper; private sourceHelper: SourceHelper; - private panel: WebviewPanel; + private panel?: WebviewPanel; private rootDir: WorkspaceFolder | undefined; private literal: ExtractedTemplateLiteral; private _projectConfig: GraphQLProjectConfig | undefined; @@ -48,7 +48,13 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { } updatePanel() { - this.panel.webview.html = this.html; + if (this.panel) { + this.panel.webview.html = this.html; + } + } + + public get hasConfig() { + return Boolean(this._projectConfig); } async getVariablesFromUser( @@ -96,7 +102,7 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { uri: Uri, outputChannel: OutputChannel, literal: ExtractedTemplateLiteral, - panel: WebviewPanel, + panel?: WebviewPanel, ) { this.uri = uri; this.outputChannel = outputChannel; @@ -106,16 +112,14 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { this.sourceHelper, ); this.panel = panel; + this.rootDir = workspace.getWorkspaceFolder(Uri.file(literal.uri)); this.literal = literal; - this.panel.webview.options = { - enableScripts: true, - }; - - // eslint-disable-next-line promise/prefer-await-to-then -- can't use async in constructor - this.loadProvider().catch(err => { - this.html = err.toString(); - }); + if (this.panel) { + this.panel.webview.options = { + enableScripts: true, + }; + } } validUrlFromSchema(pathOrUrl: string) { @@ -169,7 +173,6 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { } } const endpointNames = Object.keys(endpoints); - if (endpointNames.length === 0) { this.reportError( 'Error: endpoint data missing from graphql config endpoints extension', @@ -252,7 +255,6 @@ export class GraphQLContentProvider implements TextDocumentContentProvider { this.reportError('Error: this file is outside the workspace.'); return; } - const config = await loadConfig({ rootDir: rootDir.uri.fsPath, throwOnEmpty: false,