Skip to content

Commit

Permalink
wip: feat: Add getDefinitionAtPosition
Browse files Browse the repository at this point in the history
  • Loading branch information
Quramy committed Mar 19, 2024
1 parent b4a5048 commit 940263b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 1 deletion.
@@ -0,0 +1,44 @@
import ts from 'typescript';
import { mark, type Frets } from 'fretted-strings';
import { AdapterFixture } from './testing/adapter-fixture';
import { getDefinitionAtPosition } from './get-definition-at-position';

function createFixture(name: string) {
return new AdapterFixture(name);
}

describe(getDefinitionAtPosition, () => {
const delegateFn = jest.fn(() => []);
it('should works', () => {
const fixture = createFixture('input.ts');
const frets: Frets = {};
fixture.source = mark(
`
const query = \`
query MyQuery {
...MyFragment
%%% ^ %%%
%%% cur %%%
}
fragment MyFragment on Query {
%%% ^ ^ %%%
%%% d1 d2 %%%
__typename
}
\`
`,
frets,
);
const actual = fixture.adapter.getDefinitionAtPosition(delegateFn, 'input.ts', frets.cur.pos);
expect(actual).toMatchObject([
{
fileName: 'input.ts',
textSpan: {
start: frets.d1.pos,
length: frets.d2.pos - frets.d1.pos,
},
},
] as Partial<ts.DefinitionInfo>[]);
});
});
54 changes: 54 additions & 0 deletions src/graphql-language-service-adapter/get-definition-at-position.ts
@@ -0,0 +1,54 @@
import ts from 'typescript';
import { visit, type FragmentSpreadNode } from 'graphql';
import { getFragmentsInDocument } from '../gql-ast-util';
import type { AnalysisContext, GetDefinitionAtPosition } from './types';

export function getDefinitionAtPosition(
ctx: AnalysisContext,
delegate: GetDefinitionAtPosition,
fileName: string,
position: number,
) {
if (ctx.getScriptSourceHelper().isExcluded(fileName)) return delegate(fileName, position);
const node = ctx.findAscendantTemplateNode(fileName, position);
if (!node) return delegate(fileName, position);
const { resolvedInfo } = ctx.resolveTemplateInfo(fileName, node);
if (!resolvedInfo) return delegate(fileName, position);
const { combinedText, getInnerPosition, getSourcePosition } = resolvedInfo;
const documentNode = ctx.getGraphQLDocumentNode(combinedText);
if (!documentNode) return delegate(fileName, position);

try {
const innerPosition = getInnerPosition(position);
if (innerPosition.isInOtherExpression) return delegate(fileName, position);
let fragmentSpreadNodeUnderCursor: FragmentSpreadNode | undefined = undefined;
visit(documentNode, {
FragmentSpread: node => {
if (node.loc!.start <= innerPosition.pos && innerPosition.pos < node.loc!.end) {
fragmentSpreadNodeUnderCursor = node;
}
},
});
if (!fragmentSpreadNodeUnderCursor) return delegate(fileName, position);
const foundFragmentDefinitionNode = getFragmentsInDocument(documentNode).find(
fragmentDef => fragmentDef.name.value === fragmentSpreadNodeUnderCursor?.name.value,
);
if (!foundFragmentDefinitionNode) return delegate(fileName, position);
const definitionSourcePosition = getSourcePosition(foundFragmentDefinitionNode.name.loc!.start);
return [
{
fileName: definitionSourcePosition.fileName,
name: foundFragmentDefinitionNode.name.value,
textSpan: {
start: definitionSourcePosition.pos,
length: foundFragmentDefinitionNode.name.loc!.end - foundFragmentDefinitionNode.name.loc!.start,
},
kind: ts.ScriptElementKind.unknown,
containerKind: ts.ScriptElementKind.unknown,
containerName: '',
},
] satisfies ts.DefinitionInfo[];
} catch {
return delegate(fileName, position);
}

Check warning on line 53 in src/graphql-language-service-adapter/get-definition-at-position.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql-language-service-adapter/get-definition-at-position.ts#L52-L53

Added lines #L52 - L53 were not covered by tests
}
Expand Up @@ -9,10 +9,17 @@ import {
} from '../ts-ast-util';
import type { SchemaBuildErrorInfo } from '../schema-manager/schema-manager';
import { getFragmentNamesInDocument, detectDuplicatedFragments, type FragmentRegistry } from '../gql-ast-util';
import type { AnalysisContext, GetCompletionAtPosition, GetSemanticDiagnostics, GetQuickInfoAtPosition } from './types';
import type {
AnalysisContext,
GetCompletionAtPosition,
GetSemanticDiagnostics,
GetQuickInfoAtPosition,
GetDefinitionAtPosition,
} from './types';
import { getCompletionAtPosition } from './get-completion-at-position';
import { getSemanticDiagnostics } from './get-semantic-diagnostics';
import { getQuickInfoAtPosition } from './get-quick-info-at-position';
import { getDefinitionAtPosition } from './get-definition-at-position';
import { LRUCache } from '../cache';

export interface GraphQLLanguageServiceAdapterCreateOptions {
Expand Down Expand Up @@ -58,6 +65,10 @@ export class GraphQLLanguageServiceAdapter {
return getQuickInfoAtPosition(this._analysisContext, delegate, ...args);
}

getDefinitionAtPosition(delegate: GetDefinitionAtPosition, ...args: Parameters<GetDefinitionAtPosition>) {
return getDefinitionAtPosition(this._analysisContext, delegate, ...args);
}

updateSchema(errors: SchemaBuildErrorInfo[] | null, schema: GraphQLSchema | null) {
if (errors) {
this._schemaErrors = errors;
Expand Down
1 change: 1 addition & 0 deletions src/graphql-language-service-adapter/types.ts
Expand Up @@ -6,6 +6,7 @@ import type { SchemaBuildErrorInfo } from '../schema-manager/schema-manager';
export type GetCompletionAtPosition = ts.LanguageService['getCompletionsAtPosition'];
export type GetSemanticDiagnostics = ts.LanguageService['getSemanticDiagnostics'];
export type GetQuickInfoAtPosition = ts.LanguageService['getQuickInfoAtPosition'];
export type GetDefinitionAtPosition = ts.LanguageService['getDefinitionAtPosition'];

Check warning on line 9 in src/graphql-language-service-adapter/types.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql-language-service-adapter/types.ts#L9

Added line #L9 was not covered by tests

export interface AnalysisContext {
debug(msg: string): void;
Expand Down
1 change: 1 addition & 0 deletions src/language-service-plugin/plugin-module-factory.ts
Expand Up @@ -74,6 +74,7 @@ function create(info: ts.server.PluginCreateInfo): ts.LanguageService {
.wrap('getCompletionsAtPosition', delegate => adapter.getCompletionAtPosition.bind(adapter, delegate))
.wrap('getSemanticDiagnostics', delegate => adapter.getSemanticDiagnostics.bind(adapter, delegate))
.wrap('getQuickInfoAtPosition', delegate => adapter.getQuickInfoAtPosition.bind(adapter, delegate))
.wrap('getDefinitionAtPosition', delegate => adapter.getDefinitionAtPosition.bind(adapter, delegate))
.build();

schemaManager.registerOnChange(adapter.updateSchema.bind(adapter));
Expand Down

0 comments on commit 940263b

Please sign in to comment.