graphql-language-service
Version:
The official, runtime independent Language Service for GraphQL
196 lines (169 loc) • 5.16 kB
text/typescript
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 });
}
};