UNPKG

graphql-language-service

Version:

The official, runtime independent Language Service for GraphQL

196 lines (169 loc) 5.16 kB
import OnlineParser from '../onlineParser'; import CharacterStream from '../CharacterStream'; import { RuleKind } from '../types'; const tokenTypeMap = { Number: 'number', String: 'string', Boolean: 'builtin', Null: 'keyword', Enum: 'string-2', }; const typesMap = { ID: { value: '"1"', kind: 'StringValue', valueType: 'String' }, Int: { value: '1', kind: 'NumberValue', valueType: 'Number' }, Float: { value: '1.0', kind: 'NumberValue', valueType: 'Number' }, String: { value: '"abc"', kind: 'StringValue', valueType: 'String' }, Boolean: { value: 'true', kind: 'BooleanValue', valueType: 'Boolean' }, Enum: { value: 'ADMIN', kind: 'EnumValue', valueType: 'Enum' }, Null: { value: 'null', kind: 'NullValue', valueType: 'Null' }, }; type TokenAssertArgs = { pattern: string; kind?: RuleKind; type: RuleKind; eatSpace: boolean; }; type SimpleRules = | 'keyword' | 'name' | 'property' | 'qualifier' | 'variable' | 'meta' | 'def' | 'punctuation' | 'attribute'; type SimpleRuleAssertOptions = { kind?: RuleKind | string; }; type SimpleRule = ( pattern: string | RegExp, options?: SimpleRuleAssertOptions, ) => void; type IAssertRules = { [name in SimpleRules]: SimpleRule; } & { token: (args: TokenAssertArgs) => void; value: ( kind: RuleKind | string, pattern: string, options?: { kind?: RuleKind | string }, ) => void; eol: (eatSpace?: boolean) => void; }; type Utils = { t: IAssertRules; stream?: CharacterStream }; type Args = { name?: string; onKind?: RuleKind; args?: any[]; vars?: any[] }; export const getUtils = (source: string) => { const parser = OnlineParser(); const stream = new CharacterStream(source); const state = parser.startState(); const token = (_stream = stream, _state = state) => parser.token(_stream, _state); const t: IAssertRules = { token( { pattern, type, kind = state.kind, eatSpace = true }: TokenAssertArgs, fn = token, ) { if (eatSpace) { stream.eatSpace(); } expect(Boolean(stream.match(pattern, false))).toEqual(true); expect(fn()).toEqual(type); expect(state.kind).toEqual(kind); }, keyword(pattern, options = {}) { this.token({ pattern, type: 'keyword', kind: options.kind }); }, name(pattern, options = {}) { this.token({ pattern, type: 'atom', kind: options.kind }); }, property(pattern, options = {}) { this.token({ pattern, type: 'property', kind: options.kind }); }, qualifier(pattern, options = {}) { this.token({ pattern, type: 'qualifier', kind: options.kind }); }, variable(pattern, options = {}) { this.token({ pattern, type: 'variable', kind: options.kind }); }, meta(pattern, options = {}) { this.token({ pattern, type: 'meta', kind: options.kind }); }, def(pattern, options = {}) { this.token({ pattern, type: 'def', kind: options.kind }); }, punctuation(pattern, options = {}) { this.token({ pattern, type: 'punctuation', kind: options.kind }); }, attribute(pattern, options = {}) { this.token({ pattern, type: 'attribute', kind: options.kind }); }, value(kind, pattern, options) { this.token({ pattern, type: tokenTypeMap[kind], kind: options.kind }); }, eol(eatSpace = true) { if (eatSpace) { stream.eatSpace(); } expect(stream.eol()).toEqual(true); }, }; return { parser, token, stream, state, t }; }; export const performForEachType = (source, test) => { for (const [type, { value, kind, valueType }] of Object.entries(typesMap)) { const utils = getUtils( source.replaceAll('__VALUE__', value).replaceAll('__TYPE__', type), ); test(utils, { type, value, kind, valueType }); } }; export const expectVarsDef = ( { t, stream }: Utils, { onKind, vars = [] }: Args, ) => { t.punctuation(/\(/, { kind: 'VariableDefinitions' }); for (const variable of vars) { t.variable('$', { kind: 'Variable' }); t.variable(variable.name); t.punctuation(':', { kind: 'VariableDefinition' }); t.name(variable.type, { kind: 'NamedType' }); stream.eatWhile(/(,|\s)/); } t.punctuation(/\)/, { kind: onKind }); }; export const expectArgs = ( { t, stream }: Utils, { onKind, args = [] }: Args, ) => { t.punctuation(/\(/, { kind: 'Arguments' }); for (const arg of args) { t.attribute(arg.name, { kind: 'Argument' }); t.punctuation(':'); if (arg.isVariable) { t.variable('$', { kind: 'Variable' }); t.variable(arg.value); } else { if (arg.isList) { t.punctuation(/\[/, { kind: 'ListValue' }); } t.value(arg.valueType, arg.value, { kind: arg.kind }); if (arg.isList) { t.punctuation(/\]/, { kind: 'Arguments' }); } } stream.eatWhile(/(,|\s)/); } t.punctuation(/\)/, { kind: onKind }); }; export const expectDirective = ( utils: Utils, { name, onKind, args = [] }: Args, ) => { const { t } = utils; t.meta('@', { kind: 'Directive' }); t.meta(name); if (args.length) { expectArgs(utils, { onKind, args }); } };