merge working branch in
This commit is contained in:
commit
935c06a51b
59 changed files with 1020 additions and 464 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
# 1.1.0
|
||||||
|
* Support the GraphQL multipart spec: https://github.com/jaydenseric/graphql-multipart-request-spec
|
||||||
|
|
||||||
# 1.0.0
|
# 1.0.0
|
||||||
* Apply `package:pedantic`.
|
* Apply `package:pedantic`.
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ Future configureServer(Angel app) async {
|
||||||
convertDartType(Todo),
|
convertDartType(Todo),
|
||||||
resolve: resolveViaServiceRead(todoService),
|
resolve: resolveViaServiceRead(todoService),
|
||||||
inputs: [
|
inputs: [
|
||||||
new GraphQLFieldInput('id', graphQLId.nonNullable()),
|
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -107,7 +107,7 @@ In *development*, it's also highly recommended to mount the
|
||||||
interface, for easy querying and feedback.
|
interface, for easy querying and feedback.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
app.all('/graphql', graphQLHttp(new GraphQL(schema)));
|
app.all('/graphql', graphQLHttp(GraphQL(schema)));
|
||||||
app.get('/graphiql', graphiQL());
|
app.get('/graphiql', graphiQL());
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ All that's left now is just to start the server!
|
||||||
```dart
|
```dart
|
||||||
var server = await http.startServer('127.0.0.1', 3000);
|
var server = await http.startServer('127.0.0.1', 3000);
|
||||||
var uri =
|
var uri =
|
||||||
new Uri(scheme: 'http', host: server.address.address, port: server.port);
|
Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||||
var graphiqlUri = uri.replace(path: 'graphiql');
|
var graphiqlUri = uri.replace(path: 'graphiql');
|
||||||
print('Listening at $uri');
|
print('Listening at $uri');
|
||||||
print('Access graphiql at $graphiqlUri');
|
print('Access graphiql at $graphiqlUri');
|
||||||
|
@ -214,7 +214,7 @@ var queryType = objectType(
|
||||||
convertDartType(Todo),
|
convertDartType(Todo),
|
||||||
resolve: resolveViaServiceRead(todoService),
|
resolve: resolveViaServiceRead(todoService),
|
||||||
inputs: [
|
inputs: [
|
||||||
new GraphQLFieldInput('id', graphQLId.nonNullable()),
|
GraphQLFieldInput('id', graphQLId.nonNullable()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,46 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
export 'src/graphiql.dart';
|
export 'src/graphiql.dart';
|
||||||
export 'src/graphql_http.dart';
|
export 'src/graphql_http.dart';
|
||||||
export 'src/graphql_ws.dart';
|
export 'src/graphql_ws.dart';
|
||||||
export 'src/resolvers.dart';
|
export 'src/resolvers.dart';
|
||||||
|
|
||||||
|
/// The canonical [GraphQLUploadType] instance.
|
||||||
|
final GraphQLUploadType graphQLUpload = GraphQLUploadType();
|
||||||
|
|
||||||
|
/// A [GraphQLScalarType] that is used to read uploaded files from
|
||||||
|
/// `multipart/form-data` requests.
|
||||||
|
class GraphQLUploadType extends GraphQLScalarType<UploadedFile, UploadedFile> {
|
||||||
|
@override
|
||||||
|
String get name => 'Upload';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'Represents a file that has been uploaded to the server.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
GraphQLType<UploadedFile, UploadedFile> coerceToInputObject() => this;
|
||||||
|
|
||||||
|
@override
|
||||||
|
UploadedFile deserialize(UploadedFile serialized) => serialized;
|
||||||
|
|
||||||
|
@override
|
||||||
|
UploadedFile serialize(UploadedFile value) => value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ValidationResult<UploadedFile> validate(String key, UploadedFile input) {
|
||||||
|
if (input != null && input is! UploadedFile) {
|
||||||
|
return _Vr(false, errors: ['Expected "$key" to be a boolean.']);
|
||||||
|
}
|
||||||
|
return _Vr(true, value: input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Really need to make the validation result constructors *public*
|
||||||
|
class _Vr<T> implements ValidationResult<T> {
|
||||||
|
final bool successful;
|
||||||
|
final List<String> errors;
|
||||||
|
final T value;
|
||||||
|
|
||||||
|
_Vr(this.successful, {this.errors, this.value});
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ RequestHandler graphiQL(
|
||||||
{String graphQLEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
{String graphQLEndpoint = '/graphql', String subscriptionsEndpoint}) {
|
||||||
return (req, res) {
|
return (req, res) {
|
||||||
res
|
res
|
||||||
..contentType = new MediaType('text', 'html')
|
..contentType = MediaType('text', 'html')
|
||||||
..write(renderGraphiql(
|
..write(renderGraphiql(
|
||||||
graphqlEndpoint: graphQLEndpoint,
|
graphqlEndpoint: graphQLEndpoint,
|
||||||
subscriptionsEndpoint: subscriptionsEndpoint))
|
subscriptionsEndpoint: subscriptionsEndpoint))
|
||||||
|
@ -30,7 +30,7 @@ String renderGraphiql(
|
||||||
<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
|
<script src="//unpkg.com/graphiql-subscriptions-fetcher@0.0.2/browser/client.js"></script>
|
||||||
''';
|
''';
|
||||||
subscriptionsFetcher = '''
|
subscriptionsFetcher = '''
|
||||||
let subscriptionsClient = new window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', {
|
let subscriptionsClient = window.SubscriptionsTransportWs.SubscriptionClient('$subscriptionsEndpoint', {
|
||||||
reconnect: true
|
reconnect: true
|
||||||
});
|
});
|
||||||
let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher);
|
let $fetcherName = window.GraphiQLSubscriptionsFetcher.graphQLFetcher(subscriptionsClient, graphQLFetcher);
|
||||||
|
|
|
@ -7,15 +7,16 @@ import 'package:graphql_parser/graphql_parser.dart';
|
||||||
import 'package:graphql_schema/graphql_schema.dart';
|
import 'package:graphql_schema/graphql_schema.dart';
|
||||||
import 'package:graphql_server/graphql_server.dart';
|
import 'package:graphql_server/graphql_server.dart';
|
||||||
|
|
||||||
final ContentType graphQlContentType =
|
final ContentType graphQlContentType = ContentType('application', 'graphql');
|
||||||
new ContentType('application', 'graphql');
|
|
||||||
|
|
||||||
final Validator graphQlPostBody = new Validator({
|
final Validator graphQlPostBody = Validator({
|
||||||
'query*': isNonEmptyString,
|
'query*': isNonEmptyString,
|
||||||
'operation_name': isNonEmptyString,
|
'operation_name': isNonEmptyString,
|
||||||
'variables': predicate((v) => v == null || v is String || v is Map),
|
'variables': predicate((v) => v == null || v is String || v is Map),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final RegExp _num = RegExp(r'^[0-9]+$');
|
||||||
|
|
||||||
/// A [RequestHandler] that serves a spec-compliant GraphQL backend.
|
/// A [RequestHandler] that serves a spec-compliant GraphQL backend.
|
||||||
///
|
///
|
||||||
/// Follows the guidelines listed here:
|
/// Follows the guidelines listed here:
|
||||||
|
@ -80,30 +81,92 @@ RequestHandler graphQLHttp(GraphQL graphQL,
|
||||||
if (await validate(graphQlPostBody)(req, res) as bool) {
|
if (await validate(graphQlPostBody)(req, res) as bool) {
|
||||||
return await executeMap(req.bodyAsMap);
|
return await executeMap(req.bodyAsMap);
|
||||||
}
|
}
|
||||||
|
} else if (req.headers.contentType?.mimeType == 'multipart/form-data') {
|
||||||
|
// TODO: Support file uploads in batch requests.
|
||||||
|
var fields = await req.parseBody().then((_) => req.bodyAsMap);
|
||||||
|
var operations = fields['operations'] as String;
|
||||||
|
if (operations == null) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message: 'Missing "operations" field.');
|
||||||
|
}
|
||||||
|
var map = fields.containsKey('map')
|
||||||
|
? json.decode(fields['map'] as String)
|
||||||
|
: null;
|
||||||
|
if (map is! Map) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message: '"map" field must decode to a JSON object.');
|
||||||
|
}
|
||||||
|
var variables = Map<String, dynamic>.from(globalVariables);
|
||||||
|
for (var entry in (map as Map).entries) {
|
||||||
|
var file = req.uploadedFiles
|
||||||
|
.firstWhere((f) => f.name == entry.key, orElse: () => null);
|
||||||
|
if (file == null) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
'"map" contained key "${entry.key}", but no uploaded file '
|
||||||
|
'has that name.');
|
||||||
|
}
|
||||||
|
if (entry.value is! List) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
'The value for "${entry.key}" in the "map" field was not a JSON array.');
|
||||||
|
}
|
||||||
|
var objectPaths = entry.value as List;
|
||||||
|
for (var objectPath in objectPaths) {
|
||||||
|
var subPaths = (objectPath as String).split('.');
|
||||||
|
if (subPaths[0] == 'variables') {
|
||||||
|
Object current = variables;
|
||||||
|
for (int i = 1; i < subPaths.length; i++) {
|
||||||
|
var name = subPaths[i];
|
||||||
|
var parent = subPaths.take(i).join('.');
|
||||||
|
if (_num.hasMatch(name)) {
|
||||||
|
if (current is! List) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
'Object "$parent" is not a JSON array, but the '
|
||||||
|
'"map" field contained a mapping to $parent.$name.');
|
||||||
|
}
|
||||||
|
(current as List)[int.parse(name)] = file;
|
||||||
} else {
|
} else {
|
||||||
throw new AngelHttpException.badRequest();
|
if (current is! Map) {
|
||||||
|
throw AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
'Object "$parent" is not a JSON object, but the '
|
||||||
|
'"map" field contained a mapping to $parent.$name.');
|
||||||
|
}
|
||||||
|
(current as Map)[name] = file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new AngelHttpException.badRequest();
|
throw AngelHttpException.badRequest(
|
||||||
|
message:
|
||||||
|
'All array values in the "map" field must begin with "variables.".');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await sendGraphQLResponse(await graphQL.parseAndExecute(
|
||||||
|
operations,
|
||||||
|
sourceUrl: 'input',
|
||||||
|
globalVariables: variables,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
throw AngelHttpException.badRequest();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw AngelHttpException.badRequest();
|
||||||
}
|
}
|
||||||
} on ValidationException catch (e) {
|
} on ValidationException catch (e) {
|
||||||
var errors = <GraphQLExceptionError>[
|
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
||||||
new GraphQLExceptionError(e.message)
|
|
||||||
];
|
|
||||||
|
|
||||||
errors
|
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
||||||
.addAll(e.errors.map((ee) => new GraphQLExceptionError(ee)).toList());
|
return GraphQLException(errors).toJson();
|
||||||
return new GraphQLException(errors).toJson();
|
|
||||||
} on AngelHttpException catch (e) {
|
} on AngelHttpException catch (e) {
|
||||||
var errors = <GraphQLExceptionError>[
|
var errors = <GraphQLExceptionError>[GraphQLExceptionError(e.message)];
|
||||||
new GraphQLExceptionError(e.message)
|
|
||||||
];
|
|
||||||
|
|
||||||
errors
|
errors.addAll(e.errors.map((ee) => GraphQLExceptionError(ee)).toList());
|
||||||
.addAll(e.errors.map((ee) => new GraphQLExceptionError(ee)).toList());
|
return GraphQLException(errors).toJson();
|
||||||
return new GraphQLException(errors).toJson();
|
|
||||||
} on SyntaxError catch (e) {
|
} on SyntaxError catch (e) {
|
||||||
return new GraphQLException.fromSourceSpan(e.message, e.span);
|
return GraphQLException.fromSourceSpan(e.message, e.span);
|
||||||
} on GraphQLException catch (e) {
|
} on GraphQLException catch (e) {
|
||||||
return e.toJson();
|
return e.toJson();
|
||||||
} catch (e, st) {
|
} catch (e, st) {
|
||||||
|
@ -114,7 +177,7 @@ RequestHandler graphQLHttp(GraphQL graphQL,
|
||||||
st);
|
st);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GraphQLException.fromMessage(e.toString()).toJson();
|
return GraphQLException.fromMessage(e.toString()).toJson();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,12 @@ RequestHandler graphQLWS(GraphQL graphQL, {Duration keepAliveInterval}) {
|
||||||
await res.detach();
|
await res.detach();
|
||||||
var socket = await WebSocketTransformer.upgrade(req.rawRequest,
|
var socket = await WebSocketTransformer.upgrade(req.rawRequest,
|
||||||
protocolSelector: (protocols) {
|
protocolSelector: (protocols) {
|
||||||
if (protocols.contains('graphql-ws'))
|
if (protocols.contains('graphql-ws')) {
|
||||||
return 'graphql-ws';
|
return 'graphql-ws';
|
||||||
else
|
} else {
|
||||||
throw AngelHttpException.badRequest(
|
throw AngelHttpException.badRequest(
|
||||||
message: 'Only the "graphql-ws" protocol is allowed.');
|
message: 'Only the "graphql-ws" protocol is allowed.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
var channel = IOWebSocketChannel(socket);
|
var channel = IOWebSocketChannel(socket);
|
||||||
var client = stw.RemoteClient(channel.cast<String>());
|
var client = stw.RemoteClient(channel.cast<String>());
|
||||||
|
@ -69,6 +70,7 @@ class _GraphQLWSServer extends stw.Server {
|
||||||
operationName: operationName,
|
operationName: operationName,
|
||||||
sourceUrl: 'input',
|
sourceUrl: 'input',
|
||||||
globalVariables: globalVariables,
|
globalVariables: globalVariables,
|
||||||
|
variableValues: variables,
|
||||||
);
|
);
|
||||||
return stw.GraphQLResult(data);
|
return stw.GraphQLResult(data);
|
||||||
} on GraphQLException catch (e) {
|
} on GraphQLException catch (e) {
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 1.2.0
|
||||||
|
* Combine `ValueContext` and `VariableContext` into a single `InputValueContext` supertype.
|
||||||
|
* Add `T computeValue(Map<String, dynamic> variables);`
|
||||||
|
* Resolve [#23](https://github.com/angel-dart/graphql/issues/23).
|
||||||
|
* Deprecate old `ValueOrVariable` class, and parser/AST methods related to it.
|
||||||
|
|
||||||
# 1.1.4
|
# 1.1.4
|
||||||
* Fix broken int variable parsing - https://github.com/angel-dart/graphql/pull/32
|
* Fix broken int variable parsing - https://github.com/angel-dart/graphql/pull/32
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ import 'package:graphql_parser/graphql_parser.dart';
|
||||||
|
|
||||||
doSomething(String text) {
|
doSomething(String text) {
|
||||||
var tokens = scan(text);
|
var tokens = scan(text);
|
||||||
var parser = new Parser(tokens);
|
var parser = Parser(tokens);
|
||||||
|
|
||||||
if (parser.errors.isNotEmpty) {
|
if (parser.errors.isNotEmpty) {
|
||||||
// Handle errors...
|
// Handle errors...
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
include: package:pedantic/analysis_options.yaml
|
||||||
analyzer:
|
analyzer:
|
||||||
strong-mode:
|
strong-mode:
|
||||||
implicit-casts: false
|
implicit-casts: false
|
|
@ -11,7 +11,7 @@ final String text = '''
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
var tokens = scan(text);
|
var tokens = scan(text);
|
||||||
var parser = new Parser(tokens);
|
var parser = Parser(tokens);
|
||||||
var doc = parser.parseDocument();
|
var doc = parser.parseDocument();
|
||||||
|
|
||||||
var operation = doc.definitions.first as OperationDefinitionContext;
|
var operation = doc.definitions.first as OperationDefinitionContext;
|
||||||
|
@ -19,7 +19,7 @@ main() {
|
||||||
var projectField = operation.selectionSet.selections.first.field;
|
var projectField = operation.selectionSet.selections.first.field;
|
||||||
print(projectField.fieldName.name); // project
|
print(projectField.fieldName.name); // project
|
||||||
print(projectField.arguments.first.name); // name
|
print(projectField.arguments.first.name); // name
|
||||||
print(projectField.arguments.first.valueOrVariable.value.value); // GraphQL
|
print(projectField.arguments.first.value); // GraphQL
|
||||||
|
|
||||||
var taglineField = projectField.selectionSet.selections.first.field;
|
var taglineField = projectField.selectionSet.selections.first.field;
|
||||||
print(taglineField.fieldName.name); // tagline
|
print(taglineField.fieldName.name); // tagline
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// An alternate name for a field within a [SelectionSet].
|
||||||
class AliasContext extends Node {
|
class AliasContext extends Node {
|
||||||
final Token NAME1, COLON, NAME2;
|
/// The source tokens.
|
||||||
|
final Token nameToken1, colonToken, nameToken2;
|
||||||
|
|
||||||
AliasContext(this.NAME1, this.COLON, this.NAME2);
|
AliasContext(this.nameToken1, this.colonToken, this.nameToken2);
|
||||||
|
|
||||||
|
/// Use [nameToken1] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME1 => nameToken1;
|
||||||
|
|
||||||
|
/// Use [colonToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get COLON => colonToken;
|
||||||
|
|
||||||
|
/// Use [nameToken2] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME2 => nameToken2;
|
||||||
|
|
||||||
/// The aliased name of the value.
|
/// The aliased name of the value.
|
||||||
String get alias => NAME1.text;
|
String get alias => nameToken1.text;
|
||||||
|
|
||||||
/// The actual name of the value.
|
/// The actual name of the value.
|
||||||
String get name => NAME2.text;
|
String get name => nameToken2.text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NAME1.span.expand(COLON.span).expand(NAME2.span);
|
FileSpan get span =>
|
||||||
|
nameToken1.span.expand(colonToken.span).expand(nameToken2.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,34 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'value_or_variable.dart';
|
import 'input_value.dart';
|
||||||
|
|
||||||
|
/// An argument passed to a [FieldContext].
|
||||||
class ArgumentContext extends Node {
|
class ArgumentContext extends Node {
|
||||||
final Token NAME, COLON;
|
/// The source tokens.
|
||||||
final ValueOrVariableContext valueOrVariable;
|
final Token nameToken, colonToken;
|
||||||
|
|
||||||
ArgumentContext(this.NAME, this.COLON, this.valueOrVariable);
|
/// The value of the argument.
|
||||||
|
final InputValueContext value;
|
||||||
|
|
||||||
String get name => NAME.text;
|
ArgumentContext(this.nameToken, this.colonToken, this.value);
|
||||||
|
|
||||||
|
/// Use [value] instead.
|
||||||
|
@deprecated
|
||||||
|
InputValueContext get valueOrVariable => value;
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// Use [colonToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get COLON => colonToken;
|
||||||
|
|
||||||
|
/// The name of the argument, as a [String].
|
||||||
|
String get name => nameToken.text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span =>
|
FileSpan get span =>
|
||||||
NAME.span.expand(COLON.span).expand(valueOrVariable.span);
|
nameToken.span.expand(colonToken.span).expand(value.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'value.dart';
|
import 'input_value.dart';
|
||||||
|
|
||||||
class ListValueContext extends ValueContext {
|
/// A GraphQL list value literal.
|
||||||
final Token LBRACKET, RBRACKET;
|
class ListValueContext extends InputValueContext {
|
||||||
final List<ValueContext> values = [];
|
/// The source tokens.
|
||||||
|
final Token lBracketToken, rBracketToken;
|
||||||
|
|
||||||
ListValueContext(this.LBRACKET, this.RBRACKET);
|
/// The child values.
|
||||||
|
final List<InputValueContext> values = [];
|
||||||
|
|
||||||
|
ListValueContext(this.lBracketToken, this.rBracketToken);
|
||||||
|
|
||||||
|
/// Use [lBracketToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get LBRACKET => lBracketToken;
|
||||||
|
|
||||||
|
/// Use [rBracketToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RBRACKET => rBracketToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = values.fold<FileSpan>(LBRACKET.span, (o, v) => o.expand(v.span));
|
var out =
|
||||||
return out.expand(RBRACKET.span);
|
values.fold<FileSpan>(lBracketToken.span, (o, v) => o.expand(v.span));
|
||||||
|
return out.expand(rBracketToken.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List get value => values.map((v) => v.value).toList();
|
computeValue(Map<String, dynamic> variables) {
|
||||||
|
return values.map((v) => v.computeValue(variables)).toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export 'argument.dart';
|
||||||
export 'boolean_value.dart';
|
export 'boolean_value.dart';
|
||||||
export 'default_value.dart';
|
export 'default_value.dart';
|
||||||
export 'definition.dart';
|
export 'definition.dart';
|
||||||
|
export 'deprecated_value.dart';
|
||||||
export 'directive.dart';
|
export 'directive.dart';
|
||||||
export 'document.dart';
|
export 'document.dart';
|
||||||
export 'field.dart';
|
export 'field.dart';
|
||||||
|
@ -13,6 +14,7 @@ export 'field_name.dart';
|
||||||
export 'fragment_definition.dart';
|
export 'fragment_definition.dart';
|
||||||
export 'fragment_spread.dart';
|
export 'fragment_spread.dart';
|
||||||
export 'inline_fragment.dart';
|
export 'inline_fragment.dart';
|
||||||
|
export 'input_value.dart';
|
||||||
export 'list_type.dart';
|
export 'list_type.dart';
|
||||||
export 'misc_value.dart';
|
export 'misc_value.dart';
|
||||||
export 'node.dart';
|
export 'node.dart';
|
||||||
|
@ -24,8 +26,6 @@ export 'string_value.dart';
|
||||||
export 'type.dart';
|
export 'type.dart';
|
||||||
export 'type_condition.dart';
|
export 'type_condition.dart';
|
||||||
export 'type_name.dart';
|
export 'type_name.dart';
|
||||||
export 'value.dart';
|
|
||||||
export 'value_or_variable.dart';
|
|
||||||
export 'variable.dart';
|
export 'variable.dart';
|
||||||
export 'variable_definition.dart';
|
export 'variable_definition.dart';
|
||||||
export 'variable_definitions.dart';
|
export 'variable_definitions.dart';
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
import '../token.dart';
|
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import 'value.dart';
|
import 'input_value.dart';
|
||||||
|
import '../token.dart';
|
||||||
|
|
||||||
class BooleanValueContext extends ValueContext<bool> {
|
/// A GraphQL boolean value literal.
|
||||||
|
class BooleanValueContext extends InputValueContext<bool> {
|
||||||
bool _valueCache;
|
bool _valueCache;
|
||||||
final Token BOOLEAN;
|
|
||||||
|
|
||||||
BooleanValueContext(this.BOOLEAN) {
|
/// The source token.
|
||||||
assert(BOOLEAN?.text == 'true' || BOOLEAN?.text == 'false');
|
final Token booleanToken;
|
||||||
|
|
||||||
|
BooleanValueContext(this.booleanToken) {
|
||||||
|
assert(booleanToken?.text == 'true' || booleanToken?.text == 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get booleanValue => _valueCache ??= BOOLEAN.text == 'true';
|
/// The [bool] value of this literal.
|
||||||
|
bool get booleanValue => _valueCache ??= booleanToken.text == 'true';
|
||||||
|
|
||||||
|
/// Use [booleanToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get BOOLEAN => booleanToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get value => booleanValue;
|
FileSpan get span => booleanToken.span;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => BOOLEAN.span;
|
bool computeValue(Map<String, dynamic> variables) => booleanValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
import '../token.dart';
|
|
||||||
import 'node.dart';
|
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import 'value.dart';
|
import '../token.dart';
|
||||||
|
import 'input_value.dart';
|
||||||
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// The default value to be passed to an [ArgumentContext].
|
||||||
class DefaultValueContext extends Node {
|
class DefaultValueContext extends Node {
|
||||||
final Token EQUALS;
|
/// The source token.
|
||||||
final ValueContext value;
|
final Token equalsToken;
|
||||||
|
|
||||||
DefaultValueContext(this.EQUALS, this.value);
|
/// The default value for the argument.
|
||||||
|
final InputValueContext value;
|
||||||
|
|
||||||
|
DefaultValueContext(this.equalsToken, this.value);
|
||||||
|
|
||||||
|
/// Use [equalsToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get EQUALS => equalsToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => EQUALS.span.expand(value.span);
|
FileSpan get span => equalsToken.span.expand(value.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// The base class for top-level GraphQL definitions.
|
||||||
abstract class DefinitionContext extends Node {}
|
abstract class DefinitionContext extends Node {}
|
||||||
|
|
||||||
|
/// An executable definition.
|
||||||
abstract class ExecutableDefinitionContext extends DefinitionContext {}
|
abstract class ExecutableDefinitionContext extends DefinitionContext {}
|
||||||
|
|
||||||
|
/// An ad-hoc type system declared in GraphQL.
|
||||||
abstract class TypeSystemDefinitionContext extends DefinitionContext {}
|
abstract class TypeSystemDefinitionContext extends DefinitionContext {}
|
||||||
|
|
||||||
|
/// An extension to an existing ad-hoc type system.
|
||||||
abstract class TypeSystemExtensionContext extends DefinitionContext {}
|
abstract class TypeSystemExtensionContext extends DefinitionContext {}
|
||||||
|
|
11
graphql_parser/lib/src/language/ast/deprecated_value.dart
Normal file
11
graphql_parser/lib/src/language/ast/deprecated_value.dart
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import 'input_value.dart';
|
||||||
|
|
||||||
|
/// Use [ConstantContext] instead. This class remains solely for backwards compatibility.
|
||||||
|
@deprecated
|
||||||
|
abstract class ValueContext<T> extends InputValueContext<T> {
|
||||||
|
/// Return a constant value.
|
||||||
|
T get value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
T computeValue(Map<String, dynamic> variables) => value;
|
||||||
|
}
|
|
@ -1,27 +1,60 @@
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'argument.dart';
|
import 'argument.dart';
|
||||||
|
import 'input_value.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
import 'value_or_variable.dart';
|
|
||||||
|
|
||||||
|
/// A GraphQL directive, which may or may not have runtime semantics.
|
||||||
class DirectiveContext extends Node {
|
class DirectiveContext extends Node {
|
||||||
final Token ARROBA, NAME, COLON, LPAREN, RPAREN;
|
/// The source tokens.
|
||||||
final ArgumentContext argument;
|
final Token arrobaToken, nameToken, colonToken, lParenToken, rParenToken;
|
||||||
final ValueOrVariableContext valueOrVariable;
|
|
||||||
|
|
||||||
DirectiveContext(this.ARROBA, this.NAME, this.COLON, this.LPAREN, this.RPAREN,
|
/// The argument being passed as the directive.
|
||||||
this.argument, this.valueOrVariable) {
|
final ArgumentContext argument;
|
||||||
assert(NAME != null);
|
|
||||||
|
/// The (optional) value being passed with the directive.
|
||||||
|
final InputValueContext value;
|
||||||
|
|
||||||
|
DirectiveContext(this.arrobaToken, this.nameToken, this.colonToken,
|
||||||
|
this.lParenToken, this.rParenToken, this.argument, this.value) {
|
||||||
|
assert(nameToken != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use [value] instead.
|
||||||
|
@deprecated
|
||||||
|
InputValueContext get valueOrVariable => value;
|
||||||
|
|
||||||
|
/// Use [arrobaToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get ARROBA => arrobaToken;
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// Use [colonToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get COLON => colonToken;
|
||||||
|
|
||||||
|
/// Use [lParenToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get LPAREN => lParenToken;
|
||||||
|
|
||||||
|
/// Use [rParenToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RPAREN => rParenToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = ARROBA.span.expand(NAME.span);
|
var out = arrobaToken.span.expand(nameToken.span);
|
||||||
|
|
||||||
if (COLON != null) {
|
if (colonToken != null) {
|
||||||
out = out.expand(COLON.span).expand(valueOrVariable.span);
|
out = out.expand(colonToken.span).expand(value.span);
|
||||||
} else if (LPAREN != null) {
|
} else if (lParenToken != null) {
|
||||||
out = out.expand(LPAREN.span).expand(argument.span).expand(RPAREN.span);
|
out = out
|
||||||
|
.expand(lParenToken.span)
|
||||||
|
.expand(argument.span)
|
||||||
|
.expand(rParenToken.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -2,7 +2,9 @@ import 'package:source_span/source_span.dart';
|
||||||
import 'definition.dart';
|
import 'definition.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// A GraphQL document.
|
||||||
class DocumentContext extends Node {
|
class DocumentContext extends Node {
|
||||||
|
/// The top-level definitions in the document.
|
||||||
final List<DefinitionContext> definitions = [];
|
final List<DefinitionContext> definitions = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -5,25 +5,35 @@ import 'field_name.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'selection_set.dart';
|
import 'selection_set.dart';
|
||||||
|
|
||||||
|
/// A field in a GraphQL [SelectionSet].
|
||||||
class FieldContext extends Node {
|
class FieldContext extends Node {
|
||||||
|
/// The name of this field.
|
||||||
final FieldNameContext fieldName;
|
final FieldNameContext fieldName;
|
||||||
|
|
||||||
|
/// Any arguments this field expects.
|
||||||
final List<ArgumentContext> arguments = [];
|
final List<ArgumentContext> arguments = [];
|
||||||
|
|
||||||
|
/// Any directives affixed to this field.
|
||||||
final List<DirectiveContext> directives = [];
|
final List<DirectiveContext> directives = [];
|
||||||
|
|
||||||
|
/// The list of selections to resolve on an object.
|
||||||
final SelectionSetContext selectionSet;
|
final SelectionSetContext selectionSet;
|
||||||
|
|
||||||
FieldContext(this.fieldName, [this.selectionSet]);
|
FieldContext(this.fieldName, [this.selectionSet]);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
if (selectionSet != null)
|
if (selectionSet != null) {
|
||||||
return fieldName.span.expand(selectionSet.span);
|
return fieldName.span.expand(selectionSet.span);
|
||||||
else if (directives.isNotEmpty)
|
} else if (directives.isNotEmpty) {
|
||||||
return directives.fold<FileSpan>(
|
return directives.fold<FileSpan>(
|
||||||
fieldName.span, (out, d) => out.expand(d.span));
|
fieldName.span, (out, d) => out.expand(d.span));
|
||||||
if (arguments.isNotEmpty)
|
}
|
||||||
|
if (arguments.isNotEmpty) {
|
||||||
return arguments.fold<FileSpan>(
|
return arguments.fold<FileSpan>(
|
||||||
fieldName.span, (out, a) => out.expand(a.span));
|
fieldName.span, (out, a) => out.expand(a.span));
|
||||||
else
|
} else {
|
||||||
return fieldName.span;
|
return fieldName.span;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,25 @@ import '../token.dart';
|
||||||
import 'alias.dart';
|
import 'alias.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// The name of a GraphQL [FieldContext], which may or may not be [alias]ed.
|
||||||
class FieldNameContext extends Node {
|
class FieldNameContext extends Node {
|
||||||
final Token NAME;
|
/// The source token.
|
||||||
|
final Token nameToken;
|
||||||
|
|
||||||
|
/// An (optional) alias for the field.
|
||||||
final AliasContext alias;
|
final AliasContext alias;
|
||||||
|
|
||||||
FieldNameContext(this.NAME, [this.alias]) {
|
FieldNameContext(this.nameToken, [this.alias]) {
|
||||||
assert(NAME != null || alias != null);
|
assert(nameToken != null || alias != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
String get name => NAME?.text;
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// The [String] value of the [nameToken], if any.
|
||||||
|
String get name => nameToken?.text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => alias?.span ?? NAME.span;
|
FileSpan get span => alias?.span ?? nameToken.span;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,22 +5,43 @@ import 'package:source_span/source_span.dart';
|
||||||
import 'selection_set.dart';
|
import 'selection_set.dart';
|
||||||
import 'type_condition.dart';
|
import 'type_condition.dart';
|
||||||
|
|
||||||
|
/// A GraphQL query fragment definition.
|
||||||
class FragmentDefinitionContext extends ExecutableDefinitionContext {
|
class FragmentDefinitionContext extends ExecutableDefinitionContext {
|
||||||
final Token FRAGMENT, NAME, ON;
|
/// The source tokens.
|
||||||
|
final Token fragmentToken, nameToken, onToken;
|
||||||
|
|
||||||
|
/// The type to which this fragment applies.
|
||||||
final TypeConditionContext typeCondition;
|
final TypeConditionContext typeCondition;
|
||||||
|
|
||||||
|
/// Any directives on the fragment.
|
||||||
final List<DirectiveContext> directives = [];
|
final List<DirectiveContext> directives = [];
|
||||||
|
|
||||||
|
/// The selections to apply when the [typeCondition] is met.
|
||||||
final SelectionSetContext selectionSet;
|
final SelectionSetContext selectionSet;
|
||||||
|
|
||||||
String get name => NAME.text;
|
/// The [String] value of the [nameToken].
|
||||||
|
String get name => nameToken.text;
|
||||||
|
|
||||||
FragmentDefinitionContext(
|
FragmentDefinitionContext(this.fragmentToken, this.nameToken, this.onToken,
|
||||||
this.FRAGMENT, this.NAME, this.ON, this.typeCondition, this.selectionSet);
|
this.typeCondition, this.selectionSet);
|
||||||
|
|
||||||
|
/// Use [fragmentToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get FRAGMENT => fragmentToken;
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// Use [onToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get ON => onToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = FRAGMENT.span
|
var out = fragmentToken.span
|
||||||
.expand(NAME.span)
|
.expand(nameToken.span)
|
||||||
.expand(ON.span)
|
.expand(onToken.span)
|
||||||
.expand(typeCondition.span);
|
.expand(typeCondition.span);
|
||||||
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||||
return out.expand(selectionSet.span);
|
return out.expand(selectionSet.span);
|
||||||
|
|
|
@ -3,17 +3,30 @@ import 'directive.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
|
/// A GraphQL fragment spread.
|
||||||
class FragmentSpreadContext extends Node {
|
class FragmentSpreadContext extends Node {
|
||||||
final Token ELLIPSIS, NAME;
|
/// The source tokens.
|
||||||
|
final Token ellipsisToken, nameToken;
|
||||||
|
|
||||||
|
/// Any directives affixed to this fragment spread.
|
||||||
final List<DirectiveContext> directives = [];
|
final List<DirectiveContext> directives = [];
|
||||||
|
|
||||||
FragmentSpreadContext(this.ELLIPSIS, this.NAME);
|
FragmentSpreadContext(this.ellipsisToken, this.nameToken);
|
||||||
|
|
||||||
String get name => NAME.text;
|
/// The [String] value of the [nameToken].
|
||||||
|
String get name => nameToken.text;
|
||||||
|
|
||||||
|
/// Use [ellipsisToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get ELLIPSIS => ellipsisToken;
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = ELLIPSIS.span.expand(NAME.span);
|
var out = ellipsisToken.span.expand(nameToken.span);
|
||||||
if (directives.isEmpty) return out;
|
if (directives.isEmpty) return out;
|
||||||
return directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
return directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,35 @@ import 'package:source_span/source_span.dart';
|
||||||
import 'selection_set.dart';
|
import 'selection_set.dart';
|
||||||
import 'type_condition.dart';
|
import 'type_condition.dart';
|
||||||
|
|
||||||
|
/// An inline fragment, which typically appears in a [SelectionSetContext].
|
||||||
class InlineFragmentContext extends Node {
|
class InlineFragmentContext extends Node {
|
||||||
final Token ELLIPSIS, ON;
|
/// The source tokens.
|
||||||
|
final Token ellipsisToken, onToken;
|
||||||
|
|
||||||
|
/// The type which this fragment matches.
|
||||||
final TypeConditionContext typeCondition;
|
final TypeConditionContext typeCondition;
|
||||||
|
|
||||||
|
/// Any directives affixed to this inline fragment.
|
||||||
final List<DirectiveContext> directives = [];
|
final List<DirectiveContext> directives = [];
|
||||||
|
|
||||||
|
/// The selections applied when the [typeCondition] is met.
|
||||||
final SelectionSetContext selectionSet;
|
final SelectionSetContext selectionSet;
|
||||||
|
|
||||||
InlineFragmentContext(
|
InlineFragmentContext(
|
||||||
this.ELLIPSIS, this.ON, this.typeCondition, this.selectionSet);
|
this.ellipsisToken, this.onToken, this.typeCondition, this.selectionSet);
|
||||||
|
|
||||||
|
/// Use [ellipsisToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get ELLIPSIS => ellipsisToken;
|
||||||
|
|
||||||
|
/// Use [onToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get ON => onToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = ELLIPSIS.span.expand(ON.span).expand(typeCondition.span);
|
var out =
|
||||||
|
ellipsisToken.span.expand(onToken.span).expand(typeCondition.span);
|
||||||
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||||
return out.expand(selectionSet.span);
|
return out.expand(selectionSet.span);
|
||||||
}
|
}
|
||||||
|
|
7
graphql_parser/lib/src/language/ast/input_value.dart
Normal file
7
graphql_parser/lib/src/language/ast/input_value.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// Represents a value in GraphQL.
|
||||||
|
abstract class InputValueContext<T> extends Node {
|
||||||
|
/// Computes the value, relative to some set of [variables].
|
||||||
|
T computeValue(Map<String, dynamic> variables);
|
||||||
|
}
|
|
@ -3,12 +3,29 @@ import 'node.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import 'type.dart';
|
import 'type.dart';
|
||||||
|
|
||||||
|
/// Represents a type that holds a list of another type.
|
||||||
class ListTypeContext extends Node {
|
class ListTypeContext extends Node {
|
||||||
final Token LBRACKET, RBRACKET;
|
/// The source tokens.
|
||||||
final TypeContext type;
|
final Token lBracketToken, rBracketToken;
|
||||||
|
|
||||||
ListTypeContext(this.LBRACKET, this.type, this.RBRACKET);
|
/// The inner type.
|
||||||
|
final TypeContext innerType;
|
||||||
|
|
||||||
|
ListTypeContext(this.lBracketToken, this.innerType, this.rBracketToken);
|
||||||
|
|
||||||
|
/// Use [innerType] instead.
|
||||||
|
@deprecated
|
||||||
|
TypeContext get type => innerType;
|
||||||
|
|
||||||
|
/// Use [lBracketToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get LBRACKET => lBracketToken;
|
||||||
|
|
||||||
|
/// Use [rBracketToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RBRACKET => rBracketToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => LBRACKET.span.expand(type.span).expand(RBRACKET.span);
|
FileSpan get span =>
|
||||||
|
lBracketToken.span.expand(innerType.span).expand(rBracketToken.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,105 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
|
import 'input_value.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'value.dart';
|
|
||||||
|
|
||||||
class NullValueContext extends ValueContext<Null> {
|
/// A GraphQL `null` literal.
|
||||||
final Token NULL;
|
class NullValueContext extends InputValueContext<Null> {
|
||||||
|
/// The source token.
|
||||||
|
final Token nullToken;
|
||||||
|
|
||||||
NullValueContext(this.NULL);
|
NullValueContext(this.nullToken);
|
||||||
|
|
||||||
|
/// Use [nullToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NULL => nullToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NULL.span;
|
FileSpan get span => nullToken.span;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Null get value => null;
|
Null computeValue(Map<String, dynamic> variables) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnumValueContext extends ValueContext<String> {
|
/// A GraphQL enumeration literal.
|
||||||
final Token NAME;
|
class EnumValueContext extends InputValueContext<String> {
|
||||||
|
/// The source token.
|
||||||
|
final Token nameToken;
|
||||||
|
|
||||||
EnumValueContext(this.NAME);
|
EnumValueContext(this.nameToken);
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NAME.span;
|
FileSpan get span => nameToken.span;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get value => NAME.span.text;
|
String computeValue(Map<String, dynamic> variables) => nameToken.span.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectValueContext extends ValueContext<Map<String, dynamic>> {
|
/// A GraphQL object literal.
|
||||||
final Token LBRACE;
|
class ObjectValueContext extends InputValueContext<Map<String, dynamic>> {
|
||||||
|
/// The source tokens.
|
||||||
|
final Token lBraceToken, rBraceToken;
|
||||||
|
|
||||||
|
/// The fields in the object.
|
||||||
final List<ObjectFieldContext> fields;
|
final List<ObjectFieldContext> fields;
|
||||||
final Token RBRACE;
|
|
||||||
|
|
||||||
ObjectValueContext(this.LBRACE, this.fields, this.RBRACE);
|
ObjectValueContext(this.lBraceToken, this.fields, this.rBraceToken);
|
||||||
|
|
||||||
|
/// Use [lBraceToken] instead.
|
||||||
|
Token get LBRACE => lBraceToken;
|
||||||
|
|
||||||
|
/// Use [rBraceToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RBRACE => rBraceToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var left = LBRACE.span;
|
var left = lBraceToken.span;
|
||||||
|
|
||||||
for (var field in fields) {
|
for (var field in fields) {
|
||||||
left = left.expand(field.span);
|
left = left.expand(field.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
return left.expand(RBRACE.span);
|
return left.expand(rBraceToken.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> get value {
|
Map<String, dynamic> computeValue(Map<String, dynamic> variables) {
|
||||||
if (fields.isEmpty) {
|
if (fields.isEmpty) {
|
||||||
return <String, dynamic>{};
|
return <String, dynamic>{};
|
||||||
} else {
|
} else {
|
||||||
return fields.fold<Map<String, dynamic>>(<String, dynamic>{},
|
return fields.fold<Map<String, dynamic>>(<String, dynamic>{},
|
||||||
(map, field) {
|
(map, field) {
|
||||||
return map..[field.NAME.text] = field.value.value;
|
return map
|
||||||
|
..[field.nameToken.text] = field.value.computeValue(variables);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A field within an [ObjectValueContext].
|
||||||
class ObjectFieldContext extends Node {
|
class ObjectFieldContext extends Node {
|
||||||
final Token NAME;
|
/// The source tokens.
|
||||||
final Token COLON;
|
final Token nameToken, colonToken;
|
||||||
final ValueContext value;
|
|
||||||
|
|
||||||
ObjectFieldContext(this.NAME, this.COLON, this.value);
|
/// The associated value.
|
||||||
|
final InputValueContext value;
|
||||||
|
|
||||||
|
ObjectFieldContext(this.nameToken, this.colonToken, this.value);
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// Use [colonToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get COLON => colonToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NAME.span.expand(COLON.span).expand(value.span);
|
FileSpan get span =>
|
||||||
|
nameToken.span.expand(colonToken.span).expand(value.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'value.dart';
|
import 'input_value.dart';
|
||||||
|
|
||||||
class NumberValueContext extends ValueContext<num> {
|
/// A GraphQL number literal.
|
||||||
final Token NUMBER;
|
class NumberValueContext extends InputValueContext<num> {
|
||||||
|
/// The source token.
|
||||||
|
final Token numberToken;
|
||||||
|
|
||||||
NumberValueContext(this.NUMBER);
|
NumberValueContext(this.numberToken);
|
||||||
|
|
||||||
|
/// The [num] value of the [numberToken].
|
||||||
num get numberValue {
|
num get numberValue {
|
||||||
var text = NUMBER.text;
|
var text = numberToken.text;
|
||||||
if (!text.contains('E') && !text.contains('e'))
|
if (!text.contains('E') && !text.contains('e')) {
|
||||||
return num.parse(text);
|
return num.parse(text);
|
||||||
else {
|
} else {
|
||||||
var split = text.split(text.contains('E') ? 'E' : 'e');
|
var split = text.split(text.contains('E') ? 'E' : 'e');
|
||||||
var base = num.parse(split[0]);
|
var base = num.parse(split[0]);
|
||||||
var exp = num.parse(split[1]);
|
var exp = num.parse(split[1]);
|
||||||
|
@ -20,9 +23,13 @@ class NumberValueContext extends ValueContext<num> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
/// Use [numberToken] instead.
|
||||||
num get value => numberValue;
|
@deprecated
|
||||||
|
Token get NUMBER => numberToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NUMBER.span;
|
FileSpan get span => numberToken.span;
|
||||||
|
|
||||||
|
@override
|
||||||
|
num computeValue(Map<String, dynamic> variables) => numberValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,58 @@
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'definition.dart';
|
import 'definition.dart';
|
||||||
import 'directive.dart';
|
import 'directive.dart';
|
||||||
import 'selection_set.dart';
|
import 'selection_set.dart';
|
||||||
import 'variable_definitions.dart';
|
import 'variable_definitions.dart';
|
||||||
|
|
||||||
|
/// An executable GraphQL operation definition.
|
||||||
class OperationDefinitionContext extends ExecutableDefinitionContext {
|
class OperationDefinitionContext extends ExecutableDefinitionContext {
|
||||||
final Token TYPE, NAME;
|
/// The source tokens.
|
||||||
|
final Token typeToken, nameToken;
|
||||||
|
|
||||||
|
/// The variables defined in the operation.
|
||||||
final VariableDefinitionsContext variableDefinitions;
|
final VariableDefinitionsContext variableDefinitions;
|
||||||
|
|
||||||
|
/// Any directives affixed to this operation.
|
||||||
final List<DirectiveContext> directives = [];
|
final List<DirectiveContext> directives = [];
|
||||||
|
|
||||||
|
/// The selections to be applied to an object resolved in this operation.
|
||||||
final SelectionSetContext selectionSet;
|
final SelectionSetContext selectionSet;
|
||||||
|
|
||||||
bool get isMutation => TYPE?.text == 'mutation';
|
/// Whether this operation is a `mutation`.
|
||||||
|
bool get isMutation => typeToken?.text == 'mutation';
|
||||||
|
|
||||||
bool get isSubscription => TYPE?.text == 'subscription';
|
/// Whether this operation is a `subscription`.
|
||||||
|
bool get isSubscription => typeToken?.text == 'subscription';
|
||||||
|
|
||||||
bool get isQuery => TYPE?.text == 'query' || TYPE == null;
|
/// Whether this operation is a `query`.
|
||||||
|
bool get isQuery => typeToken?.text == 'query' || typeToken == null;
|
||||||
|
|
||||||
String get name => NAME?.text;
|
/// The [String] value of the [nameToken].
|
||||||
|
String get name => nameToken?.text;
|
||||||
|
|
||||||
OperationDefinitionContext(
|
/// Use [nameToken] instead.
|
||||||
this.TYPE, this.NAME, this.variableDefinitions, this.selectionSet) {
|
@deprecated
|
||||||
assert(TYPE == null ||
|
Token get NAME => nameToken;
|
||||||
TYPE.text == 'query' ||
|
|
||||||
TYPE.text == 'mutation' ||
|
/// Use [typeToken] instead.
|
||||||
TYPE.text == 'subscription');
|
@deprecated
|
||||||
|
Token get TYPE => typeToken;
|
||||||
|
|
||||||
|
OperationDefinitionContext(this.typeToken, this.nameToken,
|
||||||
|
this.variableDefinitions, this.selectionSet) {
|
||||||
|
assert(typeToken == null ||
|
||||||
|
typeToken.text == 'query' ||
|
||||||
|
typeToken.text == 'mutation' ||
|
||||||
|
typeToken.text == 'subscription');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
if (TYPE == null) return selectionSet.span;
|
if (typeToken == null) return selectionSet.span;
|
||||||
var out = NAME == null ? TYPE.span : TYPE.span.expand(NAME.span);
|
var out = nameToken == null
|
||||||
|
? typeToken.span
|
||||||
|
: typeToken.span.expand(nameToken.span);
|
||||||
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
out = directives.fold<FileSpan>(out, (o, d) => o.expand(d.span));
|
||||||
return out.expand(selectionSet.span);
|
return out.expand(selectionSet.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,33 @@ import '../token.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'selection.dart';
|
import 'selection.dart';
|
||||||
|
|
||||||
|
/// A set of GraphQL selections - fields, fragments, or inline fragments.
|
||||||
class SelectionSetContext extends Node {
|
class SelectionSetContext extends Node {
|
||||||
final Token LBRACE, RBRACE;
|
/// The source tokens.
|
||||||
|
final Token lBraceToken, rBraceToken;
|
||||||
|
|
||||||
|
/// The selections to be applied.
|
||||||
final List<SelectionContext> selections = [];
|
final List<SelectionContext> selections = [];
|
||||||
|
|
||||||
SelectionSetContext(this.LBRACE, this.RBRACE);
|
SelectionSetContext(this.lBraceToken, this.rBraceToken);
|
||||||
|
|
||||||
|
/// A synthetic [SelectionSetContext] produced from a set of [selections].
|
||||||
factory SelectionSetContext.merged(List<SelectionContext> selections) =
|
factory SelectionSetContext.merged(List<SelectionContext> selections) =
|
||||||
_MergedSelectionSetContext;
|
_MergedSelectionSetContext;
|
||||||
|
|
||||||
|
/// Use [lBraceToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get LBRACE => lBraceToken;
|
||||||
|
|
||||||
|
/// Use [rBraceToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RBRACE => rBraceToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out =
|
var out = selections.fold<FileSpan>(
|
||||||
selections.fold<FileSpan>(LBRACE.span, (out, s) => out.expand(s.span));
|
lBraceToken.span, (out, s) => out.expand(s.span));
|
||||||
return out.expand(RBRACE.span);
|
return out.expand(rBraceToken.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,28 +3,37 @@ import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
import '../syntax_error.dart';
|
import '../syntax_error.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
import 'value.dart';
|
import 'input_value.dart';
|
||||||
|
|
||||||
class StringValueContext extends ValueContext {
|
/// A GraphQL string value literal.
|
||||||
final Token STRING;
|
class StringValueContext extends InputValueContext<String> {
|
||||||
|
/// The source token.
|
||||||
|
final Token stringToken;
|
||||||
|
|
||||||
|
/// Whether this is a block string.
|
||||||
final bool isBlockString;
|
final bool isBlockString;
|
||||||
|
|
||||||
StringValueContext(this.STRING, {this.isBlockString: false});
|
StringValueContext(this.stringToken, {this.isBlockString = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => STRING.span;
|
FileSpan get span => stringToken.span;
|
||||||
|
|
||||||
|
/// Use [stringToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get STRING => stringToken;
|
||||||
|
|
||||||
|
/// The [String] value of the [stringToken].
|
||||||
String get stringValue {
|
String get stringValue {
|
||||||
String text;
|
String text;
|
||||||
|
|
||||||
if (!isBlockString) {
|
if (!isBlockString) {
|
||||||
text = STRING.text.substring(1, STRING.text.length - 1);
|
text = stringToken.text.substring(1, stringToken.text.length - 1);
|
||||||
} else {
|
} else {
|
||||||
text = STRING.text.substring(3, STRING.text.length - 3).trim();
|
text = stringToken.text.substring(3, stringToken.text.length - 3).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
var codeUnits = text.codeUnits;
|
var codeUnits = text.codeUnits;
|
||||||
var buf = new StringBuffer();
|
var buf = StringBuffer();
|
||||||
|
|
||||||
for (int i = 0; i < codeUnits.length; i++) {
|
for (int i = 0; i < codeUnits.length; i++) {
|
||||||
var ch = codeUnits[i];
|
var ch = codeUnits[i];
|
||||||
|
@ -35,9 +44,9 @@ class StringValueContext extends ValueContext {
|
||||||
c2 = codeUnits[++i],
|
c2 = codeUnits[++i],
|
||||||
c3 = codeUnits[++i],
|
c3 = codeUnits[++i],
|
||||||
c4 = codeUnits[++i];
|
c4 = codeUnits[++i];
|
||||||
var hexString = new String.fromCharCodes([c1, c2, c3, c4]);
|
var hexString = String.fromCharCodes([c1, c2, c3, c4]);
|
||||||
var hexNumber = int.parse(hexString, radix: 16);
|
var hexNumber = int.parse(hexString, radix: 16);
|
||||||
buf.write(new String.fromCharCode(hexNumber));
|
buf.write(String.fromCharCode(hexNumber));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +72,9 @@ class StringValueContext extends ValueContext {
|
||||||
default:
|
default:
|
||||||
buf.writeCharCode(next);
|
buf.writeCharCode(next);
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
throw new SyntaxError('Unexpected "\\" in string literal.', span);
|
throw SyntaxError('Unexpected "\\" in string literal.', span);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
buf.writeCharCode(ch);
|
buf.writeCharCode(ch);
|
||||||
}
|
}
|
||||||
|
@ -74,5 +84,5 @@ class StringValueContext extends ValueContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get value => stringValue;
|
String computeValue(Map<String, dynamic> variables) => stringValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,31 @@ import 'list_type.dart';
|
||||||
import 'node.dart';
|
import 'node.dart';
|
||||||
import 'type_name.dart';
|
import 'type_name.dart';
|
||||||
|
|
||||||
|
/// A GraphQL type node.
|
||||||
class TypeContext extends Node {
|
class TypeContext extends Node {
|
||||||
final Token EXCLAMATION;
|
/// A source token, present in a nullable type literal.
|
||||||
|
final Token exclamationToken;
|
||||||
|
|
||||||
|
/// The name of the referenced type.
|
||||||
final TypeNameContext typeName;
|
final TypeNameContext typeName;
|
||||||
|
|
||||||
|
/// A list type that is being referenced.
|
||||||
final ListTypeContext listType;
|
final ListTypeContext listType;
|
||||||
|
|
||||||
bool get isNullable => EXCLAMATION == null;
|
/// Whether the type is nullable.
|
||||||
|
bool get isNullable => exclamationToken == null;
|
||||||
|
|
||||||
TypeContext(this.typeName, this.listType, [this.EXCLAMATION]) {
|
TypeContext(this.typeName, this.listType, [this.exclamationToken]) {
|
||||||
assert(typeName != null || listType != null);
|
assert(typeName != null || listType != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use [exclamationToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get EXCLAMATION => exclamationToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = typeName?.span ?? listType.span;
|
var out = typeName?.span ?? listType.span;
|
||||||
return EXCLAMATION != null ? out.expand(EXCLAMATION.span) : out;
|
return exclamationToken != null ? out.expand(exclamationToken.span) : out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,20 @@ import 'node.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import '../token.dart';
|
import '../token.dart';
|
||||||
|
|
||||||
|
/// The name of a GraphQL type.
|
||||||
class TypeNameContext extends Node {
|
class TypeNameContext extends Node {
|
||||||
final Token NAME;
|
/// The source token.
|
||||||
|
final Token nameToken;
|
||||||
|
|
||||||
String get name => NAME.text;
|
TypeNameContext(this.nameToken);
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
|
/// The [String] value of the [nameToken].
|
||||||
|
String get name => nameToken.text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => NAME.span;
|
FileSpan get span => nameToken.span;
|
||||||
|
|
||||||
TypeNameContext(this.NAME);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import 'node.dart';
|
|
||||||
|
|
||||||
abstract class ValueContext<T> extends Node {
|
|
||||||
T get value;
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import 'node.dart';
|
|
||||||
import 'package:source_span/source_span.dart';
|
|
||||||
import 'value.dart';
|
|
||||||
import 'variable.dart';
|
|
||||||
|
|
||||||
class ValueOrVariableContext extends Node {
|
|
||||||
final ValueContext value;
|
|
||||||
final VariableContext variable;
|
|
||||||
|
|
||||||
ValueOrVariableContext(this.value, this.variable) {
|
|
||||||
assert(value != null || variable != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
FileSpan get span => value?.span ?? variable.span;
|
|
||||||
}
|
|
|
@ -1,15 +1,28 @@
|
||||||
import '../token.dart';
|
|
||||||
import 'node.dart';
|
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
|
import '../token.dart';
|
||||||
|
import 'input_value.dart';
|
||||||
|
|
||||||
class VariableContext extends Node {
|
/// A variable reference in GraphQL.
|
||||||
final Token DOLLAR, NAME;
|
class VariableContext extends InputValueContext<Object> {
|
||||||
|
/// The source tokens.
|
||||||
|
final Token dollarToken, nameToken;
|
||||||
|
|
||||||
VariableContext(this.DOLLAR, this.NAME);
|
VariableContext(this.dollarToken, this.nameToken);
|
||||||
|
|
||||||
String get name => NAME.text;
|
/// The [String] value of the [nameToken].
|
||||||
|
String get name => nameToken.text;
|
||||||
|
|
||||||
|
/// Use [dollarToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get DOLLAR => dollarToken;
|
||||||
|
|
||||||
|
/// Use [nameToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get NAME => nameToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => DOLLAR.span.expand(NAME.span);
|
FileSpan get span => dollarToken.span.expand(nameToken.span);
|
||||||
// new FileSpan(DOLLAR?.span?.start, NAME?.span?.end, toSource());
|
|
||||||
|
@override
|
||||||
|
Object computeValue(Map<String, dynamic> variables) => variables[name];
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,27 @@ import 'package:source_span/source_span.dart';
|
||||||
import 'type.dart';
|
import 'type.dart';
|
||||||
import 'variable.dart';
|
import 'variable.dart';
|
||||||
|
|
||||||
|
/// A single variable definition.
|
||||||
class VariableDefinitionContext extends Node {
|
class VariableDefinitionContext extends Node {
|
||||||
final Token COLON;
|
/// The source token.
|
||||||
|
final Token colonToken;
|
||||||
|
|
||||||
|
/// The declared variable.
|
||||||
final VariableContext variable;
|
final VariableContext variable;
|
||||||
|
|
||||||
|
/// The type of the variable.
|
||||||
final TypeContext type;
|
final TypeContext type;
|
||||||
|
|
||||||
|
/// The default value of the variable.
|
||||||
final DefaultValueContext defaultValue;
|
final DefaultValueContext defaultValue;
|
||||||
|
|
||||||
VariableDefinitionContext(this.variable, this.COLON, this.type,
|
VariableDefinitionContext(this.variable, this.colonToken, this.type,
|
||||||
[this.defaultValue]);
|
[this.defaultValue]);
|
||||||
|
|
||||||
|
/// Use [colonToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get COLON => colonToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span => variable.span.expand(defaultValue?.span ?? type.span);
|
FileSpan get span => variable.span.expand(defaultValue?.span ?? type.span);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,28 @@ import 'node.dart';
|
||||||
import 'package:source_span/source_span.dart';
|
import 'package:source_span/source_span.dart';
|
||||||
import 'variable_definition.dart';
|
import 'variable_definition.dart';
|
||||||
|
|
||||||
|
/// A set of variable definitions in a GraphQL operation.
|
||||||
class VariableDefinitionsContext extends Node {
|
class VariableDefinitionsContext extends Node {
|
||||||
final Token LPAREN, RPAREN;
|
/// The source tokens.
|
||||||
|
final Token lParenToken, rParenToken;
|
||||||
|
|
||||||
|
/// The variables defined in this node.
|
||||||
final List<VariableDefinitionContext> variableDefinitions = [];
|
final List<VariableDefinitionContext> variableDefinitions = [];
|
||||||
|
|
||||||
VariableDefinitionsContext(this.LPAREN, this.RPAREN);
|
VariableDefinitionsContext(this.lParenToken, this.rParenToken);
|
||||||
|
|
||||||
|
/// Use [lParenToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get LPAREN => lParenToken;
|
||||||
|
|
||||||
|
/// Use [rParenToken] instead.
|
||||||
|
@deprecated
|
||||||
|
Token get RPAREN => rParenToken;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileSpan get span {
|
FileSpan get span {
|
||||||
var out = variableDefinitions.fold<FileSpan>(
|
var out = variableDefinitions.fold<FileSpan>(
|
||||||
LPAREN.span, (o, v) => o.expand(v.span));
|
lParenToken.span, (o, v) => o.expand(v.span));
|
||||||
return out.expand(RPAREN.span);
|
return out.expand(rParenToken.span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@ import 'syntax_error.dart';
|
||||||
import 'token.dart';
|
import 'token.dart';
|
||||||
import 'token_type.dart';
|
import 'token_type.dart';
|
||||||
|
|
||||||
final RegExp _comment = new RegExp(r'#[^\n]*');
|
final RegExp _comment = RegExp(r'#[^\n]*');
|
||||||
final RegExp _whitespace = new RegExp('[ \t\n\r]+');
|
final RegExp _whitespace = RegExp('[ \t\n\r]+');
|
||||||
// final RegExp _boolean = new RegExp(r'true|false');
|
// final RegExp _boolean = RegExp(r'true|false');
|
||||||
final RegExp _number = new RegExp(r'-?[0-9]+(\.[0-9]+)?(E|e(\+|-)?[0-9]+)?');
|
final RegExp _number = RegExp(r'-?[0-9]+(\.[0-9]+)?(E|e(\+|-)?[0-9]+)?');
|
||||||
final RegExp _string = new RegExp(
|
final RegExp _string = RegExp(
|
||||||
r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"');
|
r'"((\\(["\\/bfnrt]|(u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])))|([^"\\]))*"');
|
||||||
final RegExp _blockString = new RegExp(r'"""(([^"])|(\\"""))*"""');
|
final RegExp _blockString = RegExp(r'"""(([^"])|(\\"""))*"""');
|
||||||
final RegExp _name = new RegExp(r'[_A-Za-z][_0-9A-Za-z]*');
|
final RegExp _name = RegExp(r'[_A-Za-z][_0-9A-Za-z]*');
|
||||||
|
|
||||||
final Map<Pattern, TokenType> _patterns = {
|
final Map<Pattern, TokenType> _patterns = {
|
||||||
'@': TokenType.ARROBA,
|
'@': TokenType.ARROBA,
|
||||||
|
@ -42,7 +42,7 @@ final Map<Pattern, TokenType> _patterns = {
|
||||||
|
|
||||||
List<Token> scan(String text, {sourceUrl}) {
|
List<Token> scan(String text, {sourceUrl}) {
|
||||||
List<Token> out = [];
|
List<Token> out = [];
|
||||||
var scanner = new SpanScanner(text, sourceUrl: sourceUrl);
|
var scanner = SpanScanner(text, sourceUrl: sourceUrl);
|
||||||
|
|
||||||
while (!scanner.isDone) {
|
while (!scanner.isDone) {
|
||||||
List<Token> potential = [];
|
List<Token> potential = [];
|
||||||
|
@ -54,14 +54,14 @@ List<Token> scan(String text, {sourceUrl}) {
|
||||||
|
|
||||||
for (var pattern in _patterns.keys) {
|
for (var pattern in _patterns.keys) {
|
||||||
if (scanner.matches(pattern)) {
|
if (scanner.matches(pattern)) {
|
||||||
potential.add(new Token(
|
potential.add(
|
||||||
_patterns[pattern], scanner.lastMatch[0], scanner.lastSpan));
|
Token(_patterns[pattern], scanner.lastMatch[0], scanner.lastSpan));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (potential.isEmpty) {
|
if (potential.isEmpty) {
|
||||||
var ch = new String.fromCharCode(scanner.readChar());
|
var ch = String.fromCharCode(scanner.readChar());
|
||||||
throw new SyntaxError("Unexpected token '$ch'.", scanner.emptySpan);
|
throw SyntaxError("Unexpected token '$ch'.", scanner.emptySpan);
|
||||||
} else {
|
} else {
|
||||||
// Choose longest token
|
// Choose longest token
|
||||||
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
|
potential.sort((a, b) => b.text.length.compareTo(a.text.length));
|
||||||
|
|
|
@ -60,7 +60,7 @@ class Parser {
|
||||||
def = parseDefinition();
|
def = parseDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DocumentContext()..definitions.addAll(defs);
|
return DocumentContext()..definitions.addAll(defs);
|
||||||
}
|
}
|
||||||
|
|
||||||
DefinitionContext parseDefinition() =>
|
DefinitionContext parseDefinition() =>
|
||||||
|
@ -68,9 +68,9 @@ class Parser {
|
||||||
|
|
||||||
OperationDefinitionContext parseOperationDefinition() {
|
OperationDefinitionContext parseOperationDefinition() {
|
||||||
var selectionSet = parseSelectionSet();
|
var selectionSet = parseSelectionSet();
|
||||||
if (selectionSet != null)
|
if (selectionSet != null) {
|
||||||
return new OperationDefinitionContext(null, null, null, selectionSet);
|
return OperationDefinitionContext(null, null, null, selectionSet);
|
||||||
else {
|
} else {
|
||||||
if (nextName('mutation') ||
|
if (nextName('mutation') ||
|
||||||
nextName('query') ||
|
nextName('query') ||
|
||||||
nextName('subscription')) {
|
nextName('subscription')) {
|
||||||
|
@ -79,20 +79,20 @@ class Parser {
|
||||||
var variables = parseVariableDefinitions();
|
var variables = parseVariableDefinitions();
|
||||||
var dirs = parseDirectives();
|
var dirs = parseDirectives();
|
||||||
var selectionSet = parseSelectionSet();
|
var selectionSet = parseSelectionSet();
|
||||||
if (selectionSet != null)
|
if (selectionSet != null) {
|
||||||
return new OperationDefinitionContext(
|
return OperationDefinitionContext(TYPE, NAME, variables, selectionSet)
|
||||||
TYPE, NAME, variables, selectionSet)
|
|
||||||
..directives.addAll(dirs);
|
..directives.addAll(dirs);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing selection set in fragment definition.',
|
'Missing selection set in fragment definition.',
|
||||||
NAME?.span ?? TYPE.span));
|
NAME?.span ?? TYPE.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FragmentDefinitionContext parseFragmentDefinition() {
|
FragmentDefinitionContext parseFragmentDefinition() {
|
||||||
if (nextName('fragment')) {
|
if (nextName('fragment')) {
|
||||||
|
@ -105,52 +105,54 @@ class Parser {
|
||||||
if (typeCondition != null) {
|
if (typeCondition != null) {
|
||||||
var dirs = parseDirectives();
|
var dirs = parseDirectives();
|
||||||
var selectionSet = parseSelectionSet();
|
var selectionSet = parseSelectionSet();
|
||||||
if (selectionSet != null)
|
if (selectionSet != null) {
|
||||||
return new FragmentDefinitionContext(
|
return FragmentDefinitionContext(
|
||||||
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
FRAGMENT, NAME, ON, typeCondition, selectionSet)
|
||||||
..directives.addAll(dirs);
|
..directives.addAll(dirs);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Expected selection set in fragment definition.',
|
'Expected selection set in fragment definition.',
|
||||||
typeCondition.span));
|
typeCondition.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Expected type condition after "on" in fragment definition.',
|
'Expected type condition after "on" in fragment definition.',
|
||||||
ON.span));
|
ON.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
'Expected "on" after name "${NAME.text}" in fragment definition.',
|
||||||
NAME.span));
|
NAME.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Expected name after "fragment" in fragment definition.',
|
'Expected name after "fragment" in fragment definition.',
|
||||||
FRAGMENT.span));
|
FRAGMENT.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FragmentSpreadContext parseFragmentSpread() {
|
FragmentSpreadContext parseFragmentSpread() {
|
||||||
if (next(TokenType.ELLIPSIS)) {
|
if (next(TokenType.ELLIPSIS)) {
|
||||||
var ELLIPSIS = current;
|
var ELLIPSIS = current;
|
||||||
if (next(TokenType.NAME, exclude: ['on'])) {
|
if (next(TokenType.NAME, exclude: ['on'])) {
|
||||||
var NAME = current;
|
var NAME = current;
|
||||||
return new FragmentSpreadContext(ELLIPSIS, NAME)
|
return FragmentSpreadContext(ELLIPSIS, NAME)
|
||||||
..directives.addAll(parseDirectives());
|
..directives.addAll(parseDirectives());
|
||||||
} else {
|
} else {
|
||||||
_index--;
|
_index--;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InlineFragmentContext parseInlineFragment() {
|
InlineFragmentContext parseInlineFragment() {
|
||||||
if (next(TokenType.ELLIPSIS)) {
|
if (next(TokenType.ELLIPSIS)) {
|
||||||
|
@ -162,11 +164,11 @@ class Parser {
|
||||||
var directives = parseDirectives();
|
var directives = parseDirectives();
|
||||||
var selectionSet = parseSelectionSet();
|
var selectionSet = parseSelectionSet();
|
||||||
if (selectionSet != null) {
|
if (selectionSet != null) {
|
||||||
return new InlineFragmentContext(
|
return InlineFragmentContext(
|
||||||
ELLIPSIS, ON, typeCondition, selectionSet)
|
ELLIPSIS, ON, typeCondition, selectionSet)
|
||||||
..directives.addAll(directives);
|
..directives.addAll(directives);
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing selection set in inline fragment.',
|
'Missing selection set in inline fragment.',
|
||||||
directives.isEmpty
|
directives.isEmpty
|
||||||
? typeCondition.span
|
? typeCondition.span
|
||||||
|
@ -174,19 +176,20 @@ class Parser {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing type condition after "on" in inline fragment.',
|
'Missing type condition after "on" in inline fragment.',
|
||||||
ON.span));
|
ON.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing "on" after "..." in inline fragment.', ELLIPSIS.span));
|
'Missing "on" after "..." in inline fragment.', ELLIPSIS.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SelectionSetContext parseSelectionSet() {
|
SelectionSetContext parseSelectionSet() {
|
||||||
if (next(TokenType.LBRACE)) {
|
if (next(TokenType.LBRACE)) {
|
||||||
|
@ -201,30 +204,31 @@ class Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
eatCommas();
|
eatCommas();
|
||||||
if (next(TokenType.RBRACE))
|
if (next(TokenType.RBRACE)) {
|
||||||
return new SelectionSetContext(LBRACE, current)
|
return SelectionSetContext(LBRACE, current)
|
||||||
..selections.addAll(selections);
|
..selections.addAll(selections);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError('Missing "}" after selection set.',
|
errors.add(SyntaxError('Missing "}" after selection set.',
|
||||||
selections.isEmpty ? LBRACE.span : selections.last.span));
|
selections.isEmpty ? LBRACE.span : selections.last.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SelectionContext parseSelection() {
|
SelectionContext parseSelection() {
|
||||||
var field = parseField();
|
var field = parseField();
|
||||||
if (field != null) return new SelectionContext(field);
|
if (field != null) return SelectionContext(field);
|
||||||
var fragmentSpread = parseFragmentSpread();
|
var fragmentSpread = parseFragmentSpread();
|
||||||
if (fragmentSpread != null)
|
if (fragmentSpread != null) return SelectionContext(null, fragmentSpread);
|
||||||
return new SelectionContext(null, fragmentSpread);
|
|
||||||
var inlineFragment = parseInlineFragment();
|
var inlineFragment = parseInlineFragment();
|
||||||
if (inlineFragment != null)
|
if (inlineFragment != null) {
|
||||||
return new SelectionContext(null, null, inlineFragment);
|
return SelectionContext(null, null, inlineFragment);
|
||||||
else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FieldContext parseField() {
|
FieldContext parseField() {
|
||||||
var fieldName = parseFieldName();
|
var fieldName = parseFieldName();
|
||||||
|
@ -232,31 +236,33 @@ class Parser {
|
||||||
var args = parseArguments();
|
var args = parseArguments();
|
||||||
var directives = parseDirectives();
|
var directives = parseDirectives();
|
||||||
var selectionSet = parseSelectionSet();
|
var selectionSet = parseSelectionSet();
|
||||||
return new FieldContext(fieldName, selectionSet)
|
return FieldContext(fieldName, selectionSet)
|
||||||
..arguments.addAll(args ?? <ArgumentContext>[])
|
..arguments.addAll(args ?? <ArgumentContext>[])
|
||||||
..directives.addAll(directives);
|
..directives.addAll(directives);
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FieldNameContext parseFieldName() {
|
FieldNameContext parseFieldName() {
|
||||||
if (next(TokenType.NAME)) {
|
if (next(TokenType.NAME)) {
|
||||||
var NAME1 = current;
|
var NAME1 = current;
|
||||||
if (next(TokenType.COLON)) {
|
if (next(TokenType.COLON)) {
|
||||||
var COLON = current;
|
var COLON = current;
|
||||||
if (next(TokenType.NAME))
|
if (next(TokenType.NAME)) {
|
||||||
return new FieldNameContext(
|
return FieldNameContext(null, AliasContext(NAME1, COLON, current));
|
||||||
null, new AliasContext(NAME1, COLON, current));
|
} else {
|
||||||
else {
|
errors.add(
|
||||||
errors.add(new SyntaxError(
|
SyntaxError('Missing name after colon in alias.', COLON.span));
|
||||||
'Missing name after colon in alias.', COLON.span));
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return new FieldNameContext(NAME1);
|
return FieldNameContext(NAME1);
|
||||||
} else
|
}
|
||||||
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VariableDefinitionsContext parseVariableDefinitions() {
|
VariableDefinitionsContext parseVariableDefinitions() {
|
||||||
if (next(TokenType.LPAREN)) {
|
if (next(TokenType.LPAREN)) {
|
||||||
|
@ -270,17 +276,18 @@ class Parser {
|
||||||
def = parseVariableDefinition();
|
def = parseVariableDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next(TokenType.RPAREN))
|
if (next(TokenType.RPAREN)) {
|
||||||
return new VariableDefinitionsContext(LPAREN, current)
|
return VariableDefinitionsContext(LPAREN, current)
|
||||||
..variableDefinitions.addAll(defs);
|
..variableDefinitions.addAll(defs);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing ")" after variable definitions.', LPAREN.span));
|
'Missing ")" after variable definitions.', LPAREN.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VariableDefinitionContext parseVariableDefinition() {
|
VariableDefinitionContext parseVariableDefinition() {
|
||||||
var variable = parseVariable();
|
var variable = parseVariable();
|
||||||
|
@ -290,34 +297,35 @@ class Parser {
|
||||||
var type = parseType();
|
var type = parseType();
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
var defaultValue = parseDefaultValue();
|
var defaultValue = parseDefaultValue();
|
||||||
return new VariableDefinitionContext(
|
return VariableDefinitionContext(variable, COLON, type, defaultValue);
|
||||||
variable, COLON, type, defaultValue);
|
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(
|
||||||
'Missing type in variable definition.', COLON.span));
|
SyntaxError('Missing type in variable definition.', COLON.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(
|
||||||
'Missing ":" in variable definition.', variable.span));
|
SyntaxError('Missing ":" in variable definition.', variable.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypeContext parseType() {
|
TypeContext parseType() {
|
||||||
var name = parseTypeName();
|
var name = parseTypeName();
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
return new TypeContext(name, null, maybe(TokenType.EXCLAMATION));
|
return TypeContext(name, null, maybe(TokenType.EXCLAMATION));
|
||||||
} else {
|
} else {
|
||||||
var listType = parseListType();
|
var listType = parseListType();
|
||||||
if (listType != null) {
|
if (listType != null) {
|
||||||
return new TypeContext(null, listType, maybe(TokenType.EXCLAMATION));
|
return TypeContext(null, listType, maybe(TokenType.EXCLAMATION));
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListTypeContext parseListType() {
|
ListTypeContext parseListType() {
|
||||||
if (next(TokenType.LBRACKET)) {
|
if (next(TokenType.LBRACKET)) {
|
||||||
|
@ -325,18 +333,19 @@ class Parser {
|
||||||
var type = parseType();
|
var type = parseType();
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
if (next(TokenType.RBRACKET)) {
|
if (next(TokenType.RBRACKET)) {
|
||||||
return new ListTypeContext(LBRACKET, type, current);
|
return ListTypeContext(LBRACKET, type, current);
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Missing "]" in list type.', type.span));
|
errors.add(SyntaxError('Missing "]" in list type.', type.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Missing type after "[".', LBRACKET.span));
|
errors.add(SyntaxError('Missing type after "[".', LBRACKET.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<DirectiveContext> parseDirectives() {
|
List<DirectiveContext> parseDirectives() {
|
||||||
List<DirectiveContext> out = [];
|
List<DirectiveContext> out = [];
|
||||||
|
@ -357,12 +366,11 @@ class Parser {
|
||||||
|
|
||||||
if (next(TokenType.COLON)) {
|
if (next(TokenType.COLON)) {
|
||||||
var COLON = current;
|
var COLON = current;
|
||||||
var val = parseValueOrVariable();
|
var val = parseInputValue();
|
||||||
if (val != null)
|
if (val != null) {
|
||||||
return new DirectiveContext(
|
return DirectiveContext(ARROBA, NAME, COLON, null, null, null, val);
|
||||||
ARROBA, NAME, COLON, null, null, null, val);
|
} else {
|
||||||
else {
|
errors.add(SyntaxError(
|
||||||
errors.add(new SyntaxError(
|
|
||||||
'Missing value or variable in directive after colon.',
|
'Missing value or variable in directive after colon.',
|
||||||
COLON.span));
|
COLON.span));
|
||||||
return null;
|
return null;
|
||||||
|
@ -372,28 +380,28 @@ class Parser {
|
||||||
var arg = parseArgument();
|
var arg = parseArgument();
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
if (next(TokenType.RPAREN)) {
|
if (next(TokenType.RPAREN)) {
|
||||||
return new DirectiveContext(
|
return DirectiveContext(
|
||||||
ARROBA, NAME, null, LPAREN, current, arg, null);
|
ARROBA, NAME, null, LPAREN, current, arg, null);
|
||||||
} else {
|
} else {
|
||||||
errors.add(
|
errors.add(SyntaxError('Missing \')\'', arg.value.span));
|
||||||
new SyntaxError('Missing \')\'', arg.valueOrVariable.span));
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(
|
errors.add(
|
||||||
new SyntaxError('Missing argument in directive.', LPAREN.span));
|
SyntaxError('Missing argument in directive.', LPAREN.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
return new DirectiveContext(
|
|
||||||
ARROBA, NAME, null, null, null, null, null);
|
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Missing name for directive.', ARROBA.span));
|
return DirectiveContext(ARROBA, NAME, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors.add(SyntaxError('Missing name for directive.', ARROBA.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<ArgumentContext> parseArguments() {
|
List<ArgumentContext> parseArguments() {
|
||||||
if (next(TokenType.LPAREN)) {
|
if (next(TokenType.LPAREN)) {
|
||||||
|
@ -407,151 +415,152 @@ class Parser {
|
||||||
arg = parseArgument();
|
arg = parseArgument();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next(TokenType.RPAREN))
|
if (next(TokenType.RPAREN)) {
|
||||||
return out;
|
return out;
|
||||||
else {
|
} else {
|
||||||
errors
|
errors.add(SyntaxError('Missing ")" in argument list.', LPAREN.span));
|
||||||
.add(new SyntaxError('Missing ")" in argument list.', LPAREN.span));
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ArgumentContext parseArgument() {
|
ArgumentContext parseArgument() {
|
||||||
if (next(TokenType.NAME)) {
|
if (next(TokenType.NAME)) {
|
||||||
var NAME = current;
|
var NAME = current;
|
||||||
if (next(TokenType.COLON)) {
|
if (next(TokenType.COLON)) {
|
||||||
var COLON = current;
|
var COLON = current;
|
||||||
var val = parseValueOrVariable();
|
var val = parseInputValue();
|
||||||
if (val != null)
|
if (val != null) {
|
||||||
return new ArgumentContext(NAME, COLON, val);
|
return ArgumentContext(NAME, COLON, val);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing value or variable in argument.', COLON.span));
|
'Missing value or variable in argument.', COLON.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(
|
||||||
'Missing colon after name in argument.', NAME.span));
|
SyntaxError('Missing colon after name in argument.', NAME.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValueOrVariableContext parseValueOrVariable() {
|
/// Use [parseInputValue] instead.
|
||||||
var value = parseValue();
|
@deprecated
|
||||||
if (value != null)
|
InputValueContext parseValueOrVariable() => parseInputValue();
|
||||||
return new ValueOrVariableContext(value, null);
|
|
||||||
else {
|
|
||||||
var variable = parseVariable();
|
|
||||||
if (variable != null)
|
|
||||||
return new ValueOrVariableContext(null, variable);
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VariableContext parseVariable() {
|
VariableContext parseVariable() {
|
||||||
if (next(TokenType.DOLLAR)) {
|
if (next(TokenType.DOLLAR)) {
|
||||||
var DOLLAR = current;
|
var DOLLAR = current;
|
||||||
if (next(TokenType.NAME))
|
if (next(TokenType.NAME)) {
|
||||||
return new VariableContext(DOLLAR, current);
|
return VariableContext(DOLLAR, current);
|
||||||
else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing name for variable; found a lone "\$" instead.',
|
'Missing name for variable; found a lone "\$" instead.',
|
||||||
DOLLAR.span));
|
DOLLAR.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DefaultValueContext parseDefaultValue() {
|
DefaultValueContext parseDefaultValue() {
|
||||||
if (next(TokenType.EQUALS)) {
|
if (next(TokenType.EQUALS)) {
|
||||||
var EQUALS = current;
|
var EQUALS = current;
|
||||||
var value = parseValue();
|
var value = parseInputValue();
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return new DefaultValueContext(EQUALS, value);
|
return DefaultValueContext(EQUALS, value);
|
||||||
} else {
|
} else {
|
||||||
errors
|
errors.add(SyntaxError('Missing value after "=" sign.', EQUALS.span));
|
||||||
.add(new SyntaxError('Missing value after "=" sign.', EQUALS.span));
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypeConditionContext parseTypeCondition() {
|
TypeConditionContext parseTypeCondition() {
|
||||||
var name = parseTypeName();
|
var name = parseTypeName();
|
||||||
if (name != null)
|
if (name != null) {
|
||||||
return new TypeConditionContext(name);
|
return TypeConditionContext(name);
|
||||||
else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TypeNameContext parseTypeName() {
|
TypeNameContext parseTypeName() {
|
||||||
if (next(TokenType.NAME)) {
|
if (next(TokenType.NAME)) {
|
||||||
return new TypeNameContext(current);
|
return TypeNameContext(current);
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValueContext parseValue() {
|
/// Use [parseInputValue] instead.
|
||||||
return (parseNumberValue() ??
|
@deprecated
|
||||||
|
InputValueContext parseValue() => parseInputValue();
|
||||||
|
|
||||||
|
InputValueContext parseInputValue() {
|
||||||
|
return (parseVariable() ??
|
||||||
|
parseNumberValue() ??
|
||||||
parseStringValue() ??
|
parseStringValue() ??
|
||||||
parseBooleanValue() ??
|
parseBooleanValue() ??
|
||||||
parseNullValue() ??
|
parseNullValue() ??
|
||||||
parseEnumValue() ??
|
parseEnumValue() ??
|
||||||
parseListValue() ??
|
parseListValue() ??
|
||||||
parseObjectValue()) as ValueContext;
|
parseObjectValue()) as InputValueContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringValueContext parseStringValue() => next(TokenType.STRING)
|
StringValueContext parseStringValue() => next(TokenType.STRING)
|
||||||
? new StringValueContext(current)
|
? StringValueContext(current)
|
||||||
: (next(TokenType.BLOCK_STRING)
|
: (next(TokenType.BLOCK_STRING)
|
||||||
? new StringValueContext(current, isBlockString: true)
|
? StringValueContext(current, isBlockString: true)
|
||||||
: null);
|
: null);
|
||||||
|
|
||||||
NumberValueContext parseNumberValue() =>
|
NumberValueContext parseNumberValue() =>
|
||||||
next(TokenType.NUMBER) ? new NumberValueContext(current) : null;
|
next(TokenType.NUMBER) ? NumberValueContext(current) : null;
|
||||||
|
|
||||||
BooleanValueContext parseBooleanValue() =>
|
BooleanValueContext parseBooleanValue() =>
|
||||||
(nextName('true') || nextName('false'))
|
(nextName('true') || nextName('false'))
|
||||||
? new BooleanValueContext(current)
|
? BooleanValueContext(current)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
EnumValueContext parseEnumValue() =>
|
EnumValueContext parseEnumValue() =>
|
||||||
next(TokenType.NAME) ? new EnumValueContext(current) : null;
|
next(TokenType.NAME) ? EnumValueContext(current) : null;
|
||||||
|
|
||||||
NullValueContext parseNullValue() =>
|
NullValueContext parseNullValue() =>
|
||||||
nextName('null') ? new NullValueContext(current) : null;
|
nextName('null') ? NullValueContext(current) : null;
|
||||||
|
|
||||||
ListValueContext parseListValue() {
|
ListValueContext parseListValue() {
|
||||||
if (next(TokenType.LBRACKET)) {
|
if (next(TokenType.LBRACKET)) {
|
||||||
var LBRACKET = current;
|
var LBRACKET = current;
|
||||||
var lastSpan = LBRACKET.span;
|
var lastSpan = LBRACKET.span;
|
||||||
List<ValueContext> values = [];
|
List<InputValueContext> values = [];
|
||||||
ValueContext value = parseValue();
|
var value = parseInputValue();
|
||||||
|
|
||||||
while (value != null) {
|
while (value != null) {
|
||||||
lastSpan = value.span;
|
lastSpan = value.span;
|
||||||
values.add(value);
|
values.add(value);
|
||||||
eatCommas();
|
eatCommas();
|
||||||
value = parseValue();
|
value = parseInputValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
eatCommas();
|
eatCommas();
|
||||||
if (next(TokenType.RBRACKET)) {
|
if (next(TokenType.RBRACKET)) {
|
||||||
return new ListValueContext(LBRACKET, current)..values.addAll(values);
|
return ListValueContext(LBRACKET, current)..values.addAll(values);
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Unterminated list literal.', lastSpan));
|
errors.add(SyntaxError('Unterminated list literal.', lastSpan));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ObjectValueContext parseObjectValue() {
|
ObjectValueContext parseObjectValue() {
|
||||||
if (next(TokenType.LBRACE)) {
|
if (next(TokenType.LBRACE)) {
|
||||||
|
@ -570,9 +579,9 @@ class Parser {
|
||||||
eatCommas();
|
eatCommas();
|
||||||
|
|
||||||
if (next(TokenType.RBRACE)) {
|
if (next(TokenType.RBRACE)) {
|
||||||
return new ObjectValueContext(LBRACE, fields, current);
|
return ObjectValueContext(LBRACE, fields, current);
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Unterminated object literal.', lastSpan));
|
errors.add(SyntaxError('Unterminated object literal.', lastSpan));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -586,16 +595,16 @@ class Parser {
|
||||||
|
|
||||||
if (next(TokenType.COLON)) {
|
if (next(TokenType.COLON)) {
|
||||||
var COLON = current;
|
var COLON = current;
|
||||||
var value = parseValue();
|
var value = parseInputValue();
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
return new ObjectFieldContext(NAME, COLON, value);
|
return ObjectFieldContext(NAME, COLON, value);
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError('Missing value after ":".', COLON.span));
|
errors.add(SyntaxError('Missing value after ":".', COLON.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.add(new SyntaxError(
|
errors.add(SyntaxError(
|
||||||
'Missing ":" after name "${NAME.span.text}".', NAME.span));
|
'Missing ":" after name "${NAME.span.text}".', NAME.span));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,10 @@ class Token {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
if (span == null)
|
if (span == null) {
|
||||||
return "'$text' -> $type";
|
return "'$text' -> $type";
|
||||||
else
|
} else {
|
||||||
return "(${span.start.line}:${span.start.column}) '$text' -> $type";
|
return "(${span.start.line}:${span.start.column}) '$text' -> $type";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,4 +11,5 @@ dependencies:
|
||||||
string_scanner: ^1.0.0
|
string_scanner: ^1.0.0
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
matcher: any
|
matcher: any
|
||||||
|
pedantic: ^1.0.0
|
||||||
test: ">=0.12.0 <2.0.0"
|
test: ">=0.12.0 <2.0.0"
|
||||||
|
|
|
@ -32,10 +32,9 @@ ArgumentContext parseArgument(String text) => parse(text).parseArgument();
|
||||||
List<ArgumentContext> parseArgumentList(String text) =>
|
List<ArgumentContext> parseArgumentList(String text) =>
|
||||||
parse(text).parseArguments();
|
parse(text).parseArguments();
|
||||||
|
|
||||||
Matcher isArgument(String name, value) => new _IsArgument(name, value);
|
Matcher isArgument(String name, value) => _IsArgument(name, value);
|
||||||
|
|
||||||
Matcher isArgumentList(List<Matcher> arguments) =>
|
Matcher isArgumentList(List<Matcher> arguments) => _IsArgumentList(arguments);
|
||||||
new _IsArgumentList(arguments);
|
|
||||||
|
|
||||||
class _IsArgument extends Matcher {
|
class _IsArgument extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -53,11 +52,11 @@ class _IsArgument extends Matcher {
|
||||||
var arg = item is ArgumentContext ? item : parseArgument(item.toString());
|
var arg = item is ArgumentContext ? item : parseArgument(item.toString());
|
||||||
if (arg == null) return false;
|
if (arg == null) return false;
|
||||||
print(arg.span.highlight());
|
print(arg.span.highlight());
|
||||||
|
|
||||||
|
var v = arg.value;
|
||||||
return equals(name).matches(arg.name, matchState) &&
|
return equals(name).matches(arg.name, matchState) &&
|
||||||
equals(value).matches(
|
((v is VariableContext && equals(value).matches(v.name, matchState)) ||
|
||||||
arg.valueOrVariable.value?.value ??
|
equals(value).matches(arg.value.computeValue({}), matchState));
|
||||||
arg.valueOrVariable.variable?.name,
|
|
||||||
matchState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
import 'package:graphql_parser/graphql_parser.dart';
|
import 'package:graphql_parser/graphql_parser.dart';
|
||||||
|
|
||||||
Parser parse(String text) => new Parser(scan(text));
|
Parser parse(String text) => Parser(scan(text));
|
||||||
|
|
|
@ -37,11 +37,10 @@ main() {
|
||||||
DirectiveContext parseDirective(String text) => parse(text).parseDirective();
|
DirectiveContext parseDirective(String text) => parse(text).parseDirective();
|
||||||
|
|
||||||
Matcher isDirective(String name, {Matcher valueOrVariable, Matcher argument}) =>
|
Matcher isDirective(String name, {Matcher valueOrVariable, Matcher argument}) =>
|
||||||
new _IsDirective(name,
|
_IsDirective(name, valueOrVariable: valueOrVariable, argument: argument);
|
||||||
valueOrVariable: valueOrVariable, argument: argument);
|
|
||||||
|
|
||||||
Matcher isDirectiveList(List<Matcher> directives) =>
|
Matcher isDirectiveList(List<Matcher> directives) =>
|
||||||
new _IsDirectiveList(directives);
|
_IsDirectiveList(directives);
|
||||||
|
|
||||||
class _IsDirective extends Matcher {
|
class _IsDirective extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -57,9 +56,10 @@ class _IsDirective extends Matcher {
|
||||||
return valueOrVariable.describe(desc.add(' and '));
|
return valueOrVariable.describe(desc.add(' and '));
|
||||||
} else if (argument != null) {
|
} else if (argument != null) {
|
||||||
return argument.describe(desc.add(' and '));
|
return argument.describe(desc.add(' and '));
|
||||||
} else
|
} else {
|
||||||
return desc;
|
return desc;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
|
@ -67,22 +67,28 @@ class _IsDirective extends Matcher {
|
||||||
item is DirectiveContext ? item : parseDirective(item.toString());
|
item is DirectiveContext ? item : parseDirective(item.toString());
|
||||||
if (directive == null) return false;
|
if (directive == null) return false;
|
||||||
if (valueOrVariable != null) {
|
if (valueOrVariable != null) {
|
||||||
if (directive.valueOrVariable == null)
|
if (directive.value == null) {
|
||||||
return false;
|
return false;
|
||||||
else
|
} else {
|
||||||
|
var v = directive.value;
|
||||||
|
if (v is VariableContext) {
|
||||||
|
return valueOrVariable.matches(v.name, matchState);
|
||||||
|
} else {
|
||||||
return valueOrVariable.matches(
|
return valueOrVariable.matches(
|
||||||
directive.valueOrVariable.value?.value ??
|
directive.value.computeValue({}), matchState);
|
||||||
directive.valueOrVariable.variable?.name,
|
}
|
||||||
matchState);
|
}
|
||||||
} else if (argument != null) {
|
} else if (argument != null) {
|
||||||
if (directive.argument == null)
|
if (directive.argument == null) {
|
||||||
return false;
|
return false;
|
||||||
else
|
} else {
|
||||||
return argument.matches(directive.argument, matchState);
|
return argument.matches(directive.argument, matchState);
|
||||||
} else
|
}
|
||||||
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _IsDirectiveList extends Matcher {
|
class _IsDirectiveList extends Matcher {
|
||||||
final List<Matcher> directives;
|
final List<Matcher> directives;
|
||||||
|
|
|
@ -86,10 +86,9 @@ Matcher isField(
|
||||||
Matcher arguments,
|
Matcher arguments,
|
||||||
Matcher directives,
|
Matcher directives,
|
||||||
Matcher selectionSet}) =>
|
Matcher selectionSet}) =>
|
||||||
new _IsField(fieldName, arguments, directives, selectionSet);
|
_IsField(fieldName, arguments, directives, selectionSet);
|
||||||
|
|
||||||
Matcher isFieldName(String name, {String alias}) =>
|
Matcher isFieldName(String name, {String alias}) => _IsFieldName(name, alias);
|
||||||
new _IsFieldName(name, alias);
|
|
||||||
|
|
||||||
class _IsField extends Matcher {
|
class _IsField extends Matcher {
|
||||||
final Matcher fieldName, arguments, directives, selectionSet;
|
final Matcher fieldName, arguments, directives, selectionSet;
|
||||||
|
@ -106,10 +105,12 @@ class _IsField extends Matcher {
|
||||||
bool matches(item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
var field = item is FieldContext ? item : parseField(item.toString());
|
var field = item is FieldContext ? item : parseField(item.toString());
|
||||||
if (field == null) return false;
|
if (field == null) return false;
|
||||||
if (fieldName != null && !fieldName.matches(field.fieldName, matchState))
|
if (fieldName != null && !fieldName.matches(field.fieldName, matchState)) {
|
||||||
return false;
|
return false;
|
||||||
if (arguments != null && !arguments.matches(field.arguments, matchState))
|
}
|
||||||
|
if (arguments != null && !arguments.matches(field.arguments, matchState)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,9 +122,10 @@ class _IsFieldName extends Matcher {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) {
|
Description describe(Description description) {
|
||||||
if (realName != null)
|
if (realName != null) {
|
||||||
return description
|
return description
|
||||||
.add('is field with name "$name" and alias "$realName"');
|
.add('is field with name "$name" and alias "$realName"');
|
||||||
|
}
|
||||||
return description.add('is field with name "$name"');
|
return description.add('is field with name "$name"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,10 +133,11 @@ class _IsFieldName extends Matcher {
|
||||||
bool matches(item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
var fieldName =
|
var fieldName =
|
||||||
item is FieldNameContext ? item : parseFieldName(item.toString());
|
item is FieldNameContext ? item : parseFieldName(item.toString());
|
||||||
if (realName != null)
|
if (realName != null) {
|
||||||
return fieldName.alias?.alias == name &&
|
return fieldName.alias?.alias == name &&
|
||||||
fieldName.alias?.name == realName;
|
fieldName.alias?.name == realName;
|
||||||
else
|
} else {
|
||||||
return fieldName.name == name;
|
return fieldName.name == name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ FragmentSpreadContext parseFragmentSpread(String text) =>
|
||||||
parse(text).parseFragmentSpread();
|
parse(text).parseFragmentSpread();
|
||||||
|
|
||||||
Matcher isFragmentSpread(String name, {Matcher directives}) =>
|
Matcher isFragmentSpread(String name, {Matcher directives}) =>
|
||||||
new _IsFragmentSpread(name, directives);
|
_IsFragmentSpread(name, directives);
|
||||||
|
|
||||||
class _IsFragmentSpread extends Matcher {
|
class _IsFragmentSpread extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
@ -35,9 +35,10 @@ class _IsFragmentSpread extends Matcher {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) {
|
Description describe(Description description) {
|
||||||
if (directives != null)
|
if (directives != null) {
|
||||||
return directives.describe(
|
return directives.describe(
|
||||||
description.add('is a fragment spread named "$name" that also '));
|
description.add('is a fragment spread named "$name" that also '));
|
||||||
|
}
|
||||||
return description.add('is a fragment spread named "$name"');
|
return description.add('is a fragment spread named "$name"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +49,10 @@ class _IsFragmentSpread extends Matcher {
|
||||||
: parseFragmentSpread(item.toString());
|
: parseFragmentSpread(item.toString());
|
||||||
if (spread == null) return false;
|
if (spread == null) return false;
|
||||||
if (spread.name != name) return false;
|
if (spread.name != name) return false;
|
||||||
if (directives != null)
|
if (directives != null) {
|
||||||
return directives.matches(spread.directives, matchState);
|
return directives.matches(spread.directives, matchState);
|
||||||
else
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ InlineFragmentContext parseInlineFragment(String text) =>
|
||||||
|
|
||||||
Matcher isInlineFragment(String name,
|
Matcher isInlineFragment(String name,
|
||||||
{Matcher directives, Matcher selectionSet}) =>
|
{Matcher directives, Matcher selectionSet}) =>
|
||||||
new _IsInlineFragment(name, directives, selectionSet);
|
_IsInlineFragment(name, directives, selectionSet);
|
||||||
|
|
||||||
class _IsInlineFragment extends Matcher {
|
class _IsInlineFragment extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
94
graphql_parser/test/issue23_test.dart
Normal file
94
graphql_parser/test/issue23_test.dart
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import 'package:graphql_parser/graphql_parser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
/// This is an *extremely* verbose test, but basically it
|
||||||
|
/// parses both documents, and makes sure that $memberId has
|
||||||
|
/// a valid value.
|
||||||
|
///
|
||||||
|
/// Resolves https://github.com/angel-dart/graphql/issues/23.
|
||||||
|
void main() {
|
||||||
|
void testStr<T>(String name, String text) {
|
||||||
|
test('name', () {
|
||||||
|
final List<Token> tokens = scan(text);
|
||||||
|
final Parser parser = Parser(tokens);
|
||||||
|
|
||||||
|
if (parser.errors.isNotEmpty) {
|
||||||
|
print(parser.errors.toString());
|
||||||
|
}
|
||||||
|
expect(parser.errors, isEmpty);
|
||||||
|
|
||||||
|
// Parse the GraphQL document using recursive descent
|
||||||
|
final DocumentContext doc = parser.parseDocument();
|
||||||
|
|
||||||
|
expect(doc.definitions, isNotNull);
|
||||||
|
expect(doc.definitions, isNotEmpty);
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
var queryDef = doc.definitions[0] as OperationDefinitionContext;
|
||||||
|
expect(queryDef.isQuery, true);
|
||||||
|
expect(queryDef.name, 'customerMemberAttributes');
|
||||||
|
expect(queryDef.variableDefinitions.variableDefinitions, hasLength(1));
|
||||||
|
var memberIdDef = queryDef.variableDefinitions.variableDefinitions[0];
|
||||||
|
expect(memberIdDef.variable.name, 'memberId');
|
||||||
|
|
||||||
|
// Find $memberId
|
||||||
|
var customerByCustomerId = queryDef.selectionSet.selections[0];
|
||||||
|
var customerMemberAttributesByCustomerId =
|
||||||
|
customerByCustomerId.field.selectionSet.selections[0];
|
||||||
|
var nodes0 =
|
||||||
|
customerMemberAttributesByCustomerId.field.selectionSet.selections[0];
|
||||||
|
var customerMemberAttributeId = nodes0.field.selectionSet.selections[0];
|
||||||
|
expect(customerMemberAttributeId.field.fieldName.name,
|
||||||
|
'customerMemberAttributeId');
|
||||||
|
var memberAttr = nodes0.field.selectionSet.selections[1];
|
||||||
|
expect(memberAttr.field.fieldName.name,
|
||||||
|
'memberAttributesByCustomerMemberAttributeId');
|
||||||
|
expect(memberAttr.field.arguments, hasLength(1));
|
||||||
|
var condition = memberAttr.field.arguments[0];
|
||||||
|
expect(condition.name, 'condition');
|
||||||
|
expect(condition.value, TypeMatcher<ObjectValueContext>());
|
||||||
|
var conditionValue = condition.value as ObjectValueContext;
|
||||||
|
var memberId = conditionValue.fields
|
||||||
|
.singleWhere((f) => f.nameToken.text == 'memberId');
|
||||||
|
expect(memberId.value, TypeMatcher<T>());
|
||||||
|
print('Found \$memberId: Instance of $T');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testStr<VariableContext>('member id as var', memberIdAsVar);
|
||||||
|
testStr<NumberValueContext>('member id as constant', memberIdAsConstant);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String memberIdAsVar = r'''
|
||||||
|
query customerMemberAttributes($memberId: Int!){
|
||||||
|
customerByCustomerId(customerId: 7) {
|
||||||
|
customerMemberAttributesByCustomerId {
|
||||||
|
nodes {
|
||||||
|
customerMemberAttributeId
|
||||||
|
memberAttributesByCustomerMemberAttributeId(condition: {memberId: $memberId}) {
|
||||||
|
nodes {
|
||||||
|
memberAttributeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
final String memberIdAsConstant = r'''
|
||||||
|
query customerMemberAttributes($memberId: Int!){
|
||||||
|
customerByCustomerId(customerId: 7) {
|
||||||
|
customerMemberAttributesByCustomerId {
|
||||||
|
nodes {
|
||||||
|
customerMemberAttributeId
|
||||||
|
memberAttributesByCustomerMemberAttributeId(condition: {memberId: 7}) {
|
||||||
|
nodes {
|
||||||
|
memberAttributeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
|
@ -55,8 +55,7 @@ main() {
|
||||||
SelectionSetContext parseSelectionSet(String text) =>
|
SelectionSetContext parseSelectionSet(String text) =>
|
||||||
parse(text).parseSelectionSet();
|
parse(text).parseSelectionSet();
|
||||||
|
|
||||||
Matcher isSelectionSet(List<Matcher> selections) =>
|
Matcher isSelectionSet(List<Matcher> selections) => _IsSelectionSet(selections);
|
||||||
new _IsSelectionSet(selections);
|
|
||||||
|
|
||||||
class _IsSelectionSet extends Matcher {
|
class _IsSelectionSet extends Matcher {
|
||||||
final List<Matcher> selections;
|
final List<Matcher> selections;
|
||||||
|
@ -87,9 +86,10 @@ class _IsSelectionSet extends Matcher {
|
||||||
for (int i = 0; i < set.selections.length; i++) {
|
for (int i = 0; i < set.selections.length; i++) {
|
||||||
var sel = set.selections[i];
|
var sel = set.selections[i];
|
||||||
if (!selections[i].matches(
|
if (!selections[i].matches(
|
||||||
sel.field ?? sel.fragmentSpread ?? sel.inlineFragment, matchState))
|
sel.field ?? sel.fragmentSpread ?? sel.inlineFragment, matchState)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ main() {
|
||||||
TypeContext parseType(String text) => parse(text).parseType();
|
TypeContext parseType(String text) => parse(text).parseType();
|
||||||
|
|
||||||
Matcher isListType(Matcher innerType, {bool isNullable}) =>
|
Matcher isListType(Matcher innerType, {bool isNullable}) =>
|
||||||
new _IsListType(innerType, isNullable: isNullable != false);
|
_IsListType(innerType, isNullable: isNullable != false);
|
||||||
|
|
||||||
Matcher isType(String name, {bool isNullable}) =>
|
Matcher isType(String name, {bool isNullable}) =>
|
||||||
new _IsType(name, nonNull: isNullable != true);
|
_IsType(name, nonNull: isNullable != true);
|
||||||
|
|
||||||
class _IsListType extends Matcher {
|
class _IsListType extends Matcher {
|
||||||
final Matcher innerType;
|
final Matcher innerType;
|
||||||
|
@ -72,7 +72,7 @@ class _IsListType extends Matcher {
|
||||||
var type = item is TypeContext ? item : parseType(item.toString());
|
var type = item is TypeContext ? item : parseType(item.toString());
|
||||||
if (type.listType == null) return false;
|
if (type.listType == null) return false;
|
||||||
if (type.isNullable != (isNullable != false)) return false;
|
if (type.isNullable != (isNullable != false)) return false;
|
||||||
return innerType.matches(type.listType.type, matchState);
|
return innerType.matches(type.listType.innerType, matchState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +84,12 @@ class _IsType extends Matcher {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) {
|
Description describe(Description description) {
|
||||||
if (nonNull == true)
|
if (nonNull == true) {
|
||||||
return description.add('is non-null type named "$name"');
|
return description.add('is non-null type named "$name"');
|
||||||
else
|
} else {
|
||||||
return description.add('is nullable type named "$name"');
|
return description.add('is nullable type named "$name"');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ main() {
|
||||||
|
|
||||||
test('exceptions', () {
|
test('exceptions', () {
|
||||||
var throwsSyntaxError = predicate((x) {
|
var throwsSyntaxError = predicate((x) {
|
||||||
var parser = parse(x.toString())..parseValue();
|
var parser = parse(x.toString())..parseInputValue();
|
||||||
return parser.errors.isNotEmpty;
|
return parser.errors.isNotEmpty;
|
||||||
}, 'fails to parse value');
|
}, 'fails to parse value');
|
||||||
|
|
||||||
|
@ -72,9 +72,9 @@ main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueContext parseValue(String text) => parse(text).parseValue();
|
InputValueContext parseValue(String text) => parse(text).parseInputValue();
|
||||||
|
|
||||||
Matcher isValue(value) => new _IsValue(value);
|
Matcher isValue(value) => _IsValue(value);
|
||||||
|
|
||||||
class _IsValue extends Matcher {
|
class _IsValue extends Matcher {
|
||||||
final value;
|
final value;
|
||||||
|
@ -87,7 +87,7 @@ class _IsValue extends Matcher {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(item, Map matchState) {
|
bool matches(item, Map matchState) {
|
||||||
var v = item is ValueContext ? item : parseValue(item.toString());
|
var v = item is InputValueContext ? item : parseValue(item.toString());
|
||||||
return equals(value).matches(v.value, matchState);
|
return equals(value).matches(v.computeValue({}), matchState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ VariableDefinitionContext parseVariableDefinition(String text) =>
|
||||||
|
|
||||||
Matcher isVariableDefinition(String name,
|
Matcher isVariableDefinition(String name,
|
||||||
{Matcher type, Matcher defaultValue}) =>
|
{Matcher type, Matcher defaultValue}) =>
|
||||||
new _IsVariableDefinition(name, type, defaultValue);
|
_IsVariableDefinition(name, type, defaultValue);
|
||||||
|
|
||||||
class _IsVariableDefinition extends Matcher {
|
class _IsVariableDefinition extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
|
@ -22,7 +22,7 @@ main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Matcher isVariable(String name) => new _IsVariable(name);
|
Matcher isVariable(String name) => _IsVariable(name);
|
||||||
|
|
||||||
class _IsVariable extends Matcher {
|
class _IsVariable extends Matcher {
|
||||||
final String name;
|
final String name;
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
# 1.1.0
|
||||||
|
* Updates for `package:graphql_parser@1.2.0`.
|
||||||
|
* Now that variables are `InputValueContext` descendants, handle them the
|
||||||
|
same way as other values in `coerceArgumentValues`. TLDR - Removed
|
||||||
|
now-obsolete, variable-specific logic in `coerceArgumentValues`.
|
||||||
|
* Pass `argumentName`, not `fieldName`, to type validations.
|
||||||
|
|
||||||
# 1.0.3
|
# 1.0.3
|
||||||
* Make field resolution asynchronous.
|
* Make field resolution asynchronous.
|
||||||
* Make introspection cycle-safe.
|
* Make introspection cycle-safe.
|
||||||
|
|
|
@ -55,7 +55,7 @@ class GraphQL {
|
||||||
|
|
||||||
GraphQLType convertType(TypeContext ctx) {
|
GraphQLType convertType(TypeContext ctx) {
|
||||||
if (ctx.listType != null) {
|
if (ctx.listType != null) {
|
||||||
return GraphQLListType(convertType(ctx.listType.type));
|
return GraphQLListType(convertType(ctx.listType.innerType));
|
||||||
} else if (ctx.typeName != null) {
|
} else if (ctx.typeName != null) {
|
||||||
switch (ctx.typeName.name) {
|
switch (ctx.typeName.name) {
|
||||||
case 'Int':
|
case 'Int':
|
||||||
|
@ -161,7 +161,8 @@ class GraphQL {
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null) {
|
||||||
coercedValues[variableName] = defaultValue.value.value;
|
coercedValues[variableName] =
|
||||||
|
defaultValue.value.computeValue(variableValues);
|
||||||
} else if (!variableType.isNullable) {
|
} else if (!variableType.isNullable) {
|
||||||
throw GraphQLException.fromSourceSpan(
|
throw GraphQLException.fromSourceSpan(
|
||||||
'Missing required variable "$variableName".',
|
'Missing required variable "$variableName".',
|
||||||
|
@ -402,25 +403,10 @@ class GraphQL {
|
||||||
var argumentType = argumentDefinition.type;
|
var argumentType = argumentDefinition.type;
|
||||||
var defaultValue = argumentDefinition.defaultValue;
|
var defaultValue = argumentDefinition.defaultValue;
|
||||||
|
|
||||||
var value = argumentValues.firstWhere((a) => a.name == argumentName,
|
var argumentValue = argumentValues
|
||||||
orElse: () => null);
|
.firstWhere((a) => a.name == argumentName, orElse: () => null);
|
||||||
|
|
||||||
if (value?.valueOrVariable?.variable != null) {
|
if (argumentValue == null) {
|
||||||
var variableName = value.valueOrVariable.variable.name;
|
|
||||||
var variableValue = variableValues[variableName];
|
|
||||||
|
|
||||||
if (variableValues.containsKey(variableName)) {
|
|
||||||
coercedValues[argumentName] = variableValue;
|
|
||||||
} else if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
|
||||||
coercedValues[argumentName] = defaultValue;
|
|
||||||
} else if (argumentType is GraphQLNonNullableType) {
|
|
||||||
throw GraphQLException.fromSourceSpan(
|
|
||||||
'Missing value for argument "$argumentName" of field "$fieldName".',
|
|
||||||
value.valueOrVariable.span);
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (value == null) {
|
|
||||||
if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
if (defaultValue != null || argumentDefinition.defaultsToNull) {
|
||||||
coercedValues[argumentName] = defaultValue;
|
coercedValues[argumentName] = defaultValue;
|
||||||
} else if (argumentType is GraphQLNonNullableType) {
|
} else if (argumentType is GraphQLNonNullableType) {
|
||||||
|
@ -432,7 +418,7 @@ class GraphQL {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
var validation = argumentType.validate(
|
var validation = argumentType.validate(
|
||||||
fieldName, value.valueOrVariable.value.value);
|
argumentName, argumentValue.value.computeValue(variableValues));
|
||||||
|
|
||||||
if (!validation.successful) {
|
if (!validation.successful) {
|
||||||
var errors = <GraphQLExceptionError>[
|
var errors = <GraphQLExceptionError>[
|
||||||
|
@ -440,7 +426,7 @@ class GraphQL {
|
||||||
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
||||||
locations: [
|
locations: [
|
||||||
GraphExceptionErrorLocation.fromSourceLocation(
|
GraphExceptionErrorLocation.fromSourceLocation(
|
||||||
value.valueOrVariable.span.start)
|
argumentValue.value.span.start)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
@ -451,7 +437,7 @@ class GraphQL {
|
||||||
error,
|
error,
|
||||||
locations: [
|
locations: [
|
||||||
GraphExceptionErrorLocation.fromSourceLocation(
|
GraphExceptionErrorLocation.fromSourceLocation(
|
||||||
value.valueOrVariable.span.start)
|
argumentValue.value.span.start)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -468,14 +454,14 @@ class GraphQL {
|
||||||
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
'Type coercion error for value of argument "$argumentName" of field "$fieldName".',
|
||||||
locations: [
|
locations: [
|
||||||
GraphExceptionErrorLocation.fromSourceLocation(
|
GraphExceptionErrorLocation.fromSourceLocation(
|
||||||
value.valueOrVariable.span.start)
|
argumentValue.value.span.start)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
GraphQLExceptionError(
|
GraphQLExceptionError(
|
||||||
e.message.toString(),
|
e.message.toString(),
|
||||||
locations: [
|
locations: [
|
||||||
GraphExceptionErrorLocation.fromSourceLocation(
|
GraphExceptionErrorLocation.fromSourceLocation(
|
||||||
value.valueOrVariable.span.start)
|
argumentValue.value.span.start)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@ -706,26 +692,28 @@ class GraphQL {
|
||||||
SelectionContext selection, Map<String, dynamic> variableValues) {
|
SelectionContext selection, Map<String, dynamic> variableValues) {
|
||||||
if (selection.field == null) return null;
|
if (selection.field == null) return null;
|
||||||
var directive = selection.field.directives.firstWhere((d) {
|
var directive = selection.field.directives.firstWhere((d) {
|
||||||
var vv = d.valueOrVariable;
|
var vv = d.value;
|
||||||
if (vv.value != null) return vv.value.value == name;
|
if (vv is VariableContext) {
|
||||||
return vv.variable.name == name;
|
return vv.name == name;
|
||||||
|
} else {
|
||||||
|
return vv.computeValue(variableValues) == name;
|
||||||
|
}
|
||||||
}, orElse: () => null);
|
}, orElse: () => null);
|
||||||
|
|
||||||
if (directive == null) return null;
|
if (directive == null) return null;
|
||||||
if (directive.argument?.name != argumentName) return null;
|
if (directive.argument?.name != argumentName) return null;
|
||||||
|
|
||||||
var vv = directive.argument.valueOrVariable;
|
var vv = directive.argument.value;
|
||||||
|
if (vv is VariableContext) {
|
||||||
if (vv.value != null) return vv.value.value;
|
var vname = vv.name;
|
||||||
|
|
||||||
var vname = vv.variable.name;
|
|
||||||
if (!variableValues.containsKey(vname)) {
|
if (!variableValues.containsKey(vname)) {
|
||||||
throw GraphQLException.fromSourceSpan(
|
throw GraphQLException.fromSourceSpan(
|
||||||
'Unknown variable: "$vname"', vv.span);
|
'Unknown variable: "$vname"', vv.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
return variableValues[vname];
|
return variableValues[vname];
|
||||||
}
|
}
|
||||||
|
return vv.computeValue(variableValues);
|
||||||
|
}
|
||||||
|
|
||||||
bool doesFragmentTypeApply(
|
bool doesFragmentTypeApply(
|
||||||
GraphQLObjectType objectType, TypeConditionContext fragmentType) {
|
GraphQLObjectType objectType, TypeConditionContext fragmentType) {
|
||||||
|
|
|
@ -370,7 +370,7 @@ GraphQLObjectType _reflectDirectiveType() {
|
||||||
field(
|
field(
|
||||||
'name',
|
'name',
|
||||||
graphQLString.nonNullable(),
|
graphQLString.nonNullable(),
|
||||||
resolve: (obj, _) => (obj as DirectiveContext).NAME.span.text,
|
resolve: (obj, _) => (obj as DirectiveContext).nameToken.span.text,
|
||||||
),
|
),
|
||||||
field(
|
field(
|
||||||
'description',
|
'description',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: graphql_server
|
name: graphql_server
|
||||||
version: 1.0.3
|
version: 1.1.0
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
description: Base package for implementing GraphQL servers. You might prefer `package:angel_graphql`, the fastest way to implement GraphQL backends in Dart.
|
description: Base package for implementing GraphQL servers. You might prefer `package:angel_graphql`, the fastest way to implement GraphQL backends in Dart.
|
||||||
homepage: https://github.com/angel-dart/graphql
|
homepage: https://github.com/angel-dart/graphql
|
||||||
|
@ -17,3 +17,6 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
pedantic: ^1.0.0
|
pedantic: ^1.0.0
|
||||||
test: ">=0.12.0 <2.0.0"
|
test: ">=0.12.0 <2.0.0"
|
||||||
|
dependency_overrides:
|
||||||
|
graphql_parser:
|
||||||
|
path: ../graphql_parser
|
||||||
|
|
Loading…
Reference in a new issue