Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

grats

Package Overview
Dependencies
Maintainers
1
Versions
240
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grats - npm Package Compare versions

Comparing version 0.0.0-main-57a88f40 to 0.0.0-main-5ac400f9

dist/src/Errors.d.ts

10

dist/package.json
{
"name": "grats",
"version": "0.0.2",
"version": "0.0.5",
"main": "dist/src/index.js",

@@ -12,3 +12,3 @@ "bin": "dist/src/cli.js",

"scripts": {
"test": "ts-node --esm src/tests/test.ts",
"test": "ts-node src/tests/test.ts",
"integration-tests": "node src/tests/integration.mjs",

@@ -38,3 +38,7 @@ "build": "tsc --build",

},
"packageManager": "pnpm@8.1.1"
"packageManager": "pnpm@8.1.1",
"engines": {
"node": ">=16 <=21",
"pnpm": "^8"
}
}
#!/usr/bin/env node
export {};
import { Location } from "graphql";
export declare function formatLoc(loc: Location): string;

@@ -40,2 +40,3 @@ #!/usr/bin/env node

exports.__esModule = true;
exports.formatLoc = void 0;
var graphql_1 = require("graphql");

@@ -49,2 +50,3 @@ var _1 = require("./");

var package_json_1 = require("../package.json");
var Locate_1 = require("./Locate");
var program = new commander_1.Command();

@@ -66,4 +68,33 @@ program

});
program
.command("locate")
.argument("<ENTITY>", "GraphQL entity to locate. E.g. `User` or `User.id`")
.option("--tsconfig <TSCONFIG>", "Path to tsconfig.json. Defaults to auto-detecting based on the current working directory")
.action(function (entity, _a) {
var tsconfig = _a.tsconfig;
var schema = buildSchema(tsconfig);
var loc = (0, Locate_1.locate)(schema, entity);
if (loc.kind === "ERROR") {
console.error(loc.err);
process.exit(1);
}
console.log(formatLoc(loc.value));
});
program.parse();
function build(output, tsconfig) {
var schema = buildSchema(tsconfig);
var sortedSchema = (0, graphql_1.lexicographicSortSchema)(schema);
var schemaStr = (0, utils_1.printSchemaWithDirectives)(sortedSchema, {
assumeValid: true
});
if (output) {
var absOutput = (0, path_1.resolve)(process.cwd(), output);
(0, fs_1.writeFileSync)(absOutput, schemaStr);
console.error("Grats: Wrote schema to `".concat(absOutput, "`."));
}
else {
console.log(schemaStr);
}
}
function buildSchema(tsconfig) {
if (tsconfig && !(0, fs_1.existsSync)(tsconfig)) {

@@ -81,12 +112,9 @@ console.error("Grats: Could not find tsconfig.json at `".concat(tsconfig, "`."));

}
var schema = (0, graphql_1.lexicographicSortSchema)(schemaResult.value);
var schemaStr = (0, utils_1.printSchemaWithDirectives)(schema, { assumeValid: true });
if (output) {
var absOutput = (0, path_1.resolve)(process.cwd(), output);
(0, fs_1.writeFileSync)(absOutput, schemaStr);
console.error("Grats: Wrote schema to `".concat(absOutput, "`."));
}
else {
console.log(schemaStr);
}
return schemaResult.value;
}
// Format a location for printing to the console. Tools like VS Code and iTerm
// will automatically turn this into a clickable link.
function formatLoc(loc) {
return "".concat(loc.source.name, ":").concat(loc.startToken.line + 1, ":").concat(loc.startToken.column + 1);
}
exports.formatLoc = formatLoc;

@@ -1,6 +0,20 @@

import { DefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, ListTypeNode, NamedTypeNode, Location as GraphQLLocation, NameNode, Token, TypeNode, NonNullTypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, ConstArgumentNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql";
import { FieldDefinitionNode, InputValueDefinitionNode, NamedTypeNode, NameNode, TypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql";
import { DiagnosticsResult } from "./utils/DiagnosticError";
import * as ts from "typescript";
import { TypeContext } from "./TypeContext";
import { GratsDefinitionNode, TypeContext } from "./TypeContext";
import { ConfigOptions } from "./lib";
import { GraphQLConstructor } from "./GraphQLConstructor";
export declare const LIBRARY_IMPORT_NAME = "grats";
export declare const LIBRARY_NAME = "Grats";
export declare const ISSUE_URL = "https://github.com/captbaritone/grats/issues";
export declare const TYPE_TAG = "gqlType";
export declare const FIELD_TAG = "gqlField";
export declare const SCALAR_TAG = "gqlScalar";
export declare const INTERFACE_TAG = "gqlInterface";
export declare const ENUM_TAG = "gqlEnum";
export declare const UNION_TAG = "gqlUnion";
export declare const INPUT_TAG = "gqlInput";
export declare const IMPLEMENTS_TAG_DEPRECATED = "gqlImplements";
export declare const KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
export declare const ALL_TAGS: string[];
type ArgDefaults = Map<string, ts.Expression>;

@@ -18,3 +32,3 @@ /**

export declare class Extractor {
definitions: DefinitionNode[];
definitions: GratsDefinitionNode[];
sourceFile: ts.SourceFile;

@@ -24,4 +38,5 @@ ctx: TypeContext;

errors: ts.Diagnostic[];
gql: GraphQLConstructor;
constructor(sourceFile: ts.SourceFile, ctx: TypeContext, buildOptions: ConfigOptions);
extract(): DiagnosticsResult<DefinitionNode[]>;
extract(): DiagnosticsResult<GratsDefinitionNode[]>;
extractType(node: ts.Node, tag: ts.JSDocTag): void;

@@ -34,4 +49,5 @@ extractScalar(node: ts.Node, tag: ts.JSDocTag): void;

/** Error handling and location juggling */
report(node: ts.Node, message: string): null;
reportUnhandled(node: ts.Node, message: string): null;
report(node: ts.Node, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null;
reportUnhandled(node: ts.Node, positionKind: "type" | "field" | "field type" | "input" | "input field" | "union member" | "constant value" | "union" | "enum value", message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null;
related(node: ts.Node, message: string): ts.DiagnosticRelatedInformation;
diagnosticAnnotatedLocation(node: ts.Node): {

@@ -42,4 +58,2 @@ start: number;

};
loc(node: ts.Node): GraphQLLocation;
gqlDummyToken(pos: number): Token;
/** TypeScript traversals */

@@ -62,5 +76,10 @@ unionTypeAliasDeclaration(node: ts.TypeAliasDeclaration, tag: ts.JSDocTag): null | undefined;

isValidTypenamePropertyType(node: ts.TypeNode, expectedName: string): boolean;
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null;
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void;
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration): Array<NamedTypeNode> | null;
reportTagInterfaces(node: ts.TypeAliasDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration): null | undefined;
collectHeritageInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null;
symbolHasGqlTag(node: ts.Node): boolean;
hasGqlTag(node: ts.Node): boolean;
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined;
collectFields(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode): Array<FieldDefinitionNode>;
constructorParam(node: ts.ParameterDeclaration): FieldDefinitionNode | null;
collectArgs(argsParam: ts.ParameterDeclaration): ReadonlyArray<InputValueDefinitionNode> | null;

@@ -70,3 +89,3 @@ collectArgDefaults(node: ts.ObjectBindingPattern): ArgDefaults;

collectArrayLiteral(node: ts.ArrayLiteralExpression): ConstListValueNode | null;
cellectObjectLiteral(node: ts.ObjectLiteralExpression): ConstObjectValueNode | null;
collectObjectLiteral(node: ts.ObjectLiteralExpression): ConstObjectValueNode | null;
collectObjectField(node: ts.ObjectLiteralElementLike): ConstObjectFieldNode | null;

@@ -78,3 +97,4 @@ collectArg(node: ts.TypeElement, defaults?: Map<string, ts.Expression> | null): InputValueDefinitionNode | null;

collectEnumValues(node: ts.EnumDeclaration): ReadonlyArray<EnumValueDefinitionNode>;
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration, tag: ts.JSDocTag): NameNode | null;
entityName(node: ts.ClassDeclaration | ts.MethodDeclaration | ts.MethodSignature | ts.PropertyDeclaration | ts.InterfaceDeclaration | ts.PropertySignature | ts.EnumDeclaration | ts.TypeAliasDeclaration | ts.FunctionDeclaration | ts.ParameterDeclaration, tag: ts.JSDocTag): NameNode | null;
validateContextParameter(node: ts.ParameterDeclaration): null | undefined;
methodDeclaration(node: ts.MethodDeclaration | ts.MethodSignature): FieldDefinitionNode | null;

@@ -93,14 +113,5 @@ collectMethodType(node: ts.TypeNode): TypeNode | null;

handleErrorBubbling(parentNode: ts.Node, type: TypeNode): TypeNode;
methodNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode;
exportDirective(nameNode: ts.Node, filename: string, functionName: string): ConstDirectiveNode;
/** GraphQL AST node helper methods */
gqlName(node: ts.Node, value: string): NameNode;
gqlNamedType(node: ts.Node, value: string): NamedTypeNode;
gqlNonNullType(node: ts.Node, type: TypeNode): NonNullTypeNode;
gqlNullableType(type: TypeNode): NamedTypeNode | ListTypeNode;
gqlListType(node: ts.Node, type: TypeNode): ListTypeNode;
gqlConstArgument(node: ts.Node, name: NameNode, value: ConstValueNode): ConstArgumentNode;
gqlConstDirective(node: ts.Node, name: NameNode, args: ReadonlyArray<ConstArgumentNode>): ConstDirectiveNode;
gqlString(node: ts.Node, value: string): StringValueNode;
fieldNameDirective(nameNode: ts.Node, name: string): ConstDirectiveNode;
}
export {};

@@ -13,4 +13,20 @@ "use strict";

};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
exports.__esModule = true;
exports.Extractor = void 0;
exports.Extractor = exports.ALL_TAGS = exports.KILLS_PARENT_ON_EXCEPTION_TAG = exports.IMPLEMENTS_TAG_DEPRECATED = exports.INPUT_TAG = exports.UNION_TAG = exports.ENUM_TAG = exports.INTERFACE_TAG = exports.SCALAR_TAG = exports.FIELD_TAG = exports.TYPE_TAG = exports.ISSUE_URL = exports.LIBRARY_NAME = exports.LIBRARY_IMPORT_NAME = void 0;
var graphql_1 = require("graphql");

@@ -20,22 +36,27 @@ var DiagnosticError_1 = require("./utils/DiagnosticError");

var TypeContext_1 = require("./TypeContext");
var E = require("./Errors");
var JSDoc_1 = require("./utils/JSDoc");
var GraphQLConstructor_1 = require("./GraphQLConstructor");
var serverDirectives_1 = require("./serverDirectives");
var LIBRARY_IMPORT_NAME = "grats";
var LIBRARY_NAME = "Grats";
var ISSUE_URL = "https://github.com/captbaritone/grats/issues";
var TYPE_TAG = "gqlType";
var FIELD_TAG = "gqlField";
var SCALAR_TAG = "gqlScalar";
var INTERFACE_TAG = "gqlInterface";
var ENUM_TAG = "gqlEnum";
var UNION_TAG = "gqlUnion";
var INPUT_TAG = "gqlInput";
var KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
var ALL_TAGS = [
TYPE_TAG,
FIELD_TAG,
SCALAR_TAG,
INTERFACE_TAG,
ENUM_TAG,
UNION_TAG,
INPUT_TAG,
exports.LIBRARY_IMPORT_NAME = "grats";
exports.LIBRARY_NAME = "Grats";
exports.ISSUE_URL = "https://github.com/captbaritone/grats/issues";
exports.TYPE_TAG = "gqlType";
exports.FIELD_TAG = "gqlField";
exports.SCALAR_TAG = "gqlScalar";
exports.INTERFACE_TAG = "gqlInterface";
exports.ENUM_TAG = "gqlEnum";
exports.UNION_TAG = "gqlUnion";
exports.INPUT_TAG = "gqlInput";
exports.IMPLEMENTS_TAG_DEPRECATED = "gqlImplements";
exports.KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";
// All the tags that start with gql
exports.ALL_TAGS = [
exports.TYPE_TAG,
exports.FIELD_TAG,
exports.SCALAR_TAG,
exports.INTERFACE_TAG,
exports.ENUM_TAG,
exports.UNION_TAG,
exports.INPUT_TAG,
];

@@ -60,2 +81,3 @@ var DEPRECATED_TAG = "deprecated";

this.configOptions = buildOptions;
this.gql = new GraphQLConstructor_1.GraphQLConstructor(sourceFile);
}

@@ -68,79 +90,71 @@ // Traverse all nodes, checking each one for its JSDoc tags.

var _this = this;
ts.forEachChild(this.sourceFile, function (node) {
var e_1, _a, e_2, _b;
try {
for (var _c = __values(ts.getJSDocTags(node)), _d = _c.next(); !_d.done; _d = _c.next()) {
var tag = _d.value;
switch (tag.tagName.text) {
case TYPE_TAG:
_this.extractType(node, tag);
break;
case SCALAR_TAG:
_this.extractScalar(node, tag);
break;
case INTERFACE_TAG:
_this.extractInterface(node, tag);
break;
case ENUM_TAG:
_this.extractEnum(node, tag);
break;
case INPUT_TAG:
_this.extractInput(node, tag);
break;
case UNION_TAG:
_this.extractUnion(node, tag);
break;
case FIELD_TAG:
if (ts.isFunctionDeclaration(node)) {
_this.functionDeclarationExtendType(node, tag);
(0, JSDoc_1.traverseJSDocTags)(this.sourceFile, function (node, tag) {
var e_1, _a;
switch (tag.tagName.text) {
case exports.TYPE_TAG:
_this.extractType(node, tag);
break;
case exports.SCALAR_TAG:
_this.extractScalar(node, tag);
break;
case exports.INTERFACE_TAG:
_this.extractInterface(node, tag);
break;
case exports.ENUM_TAG:
_this.extractEnum(node, tag);
break;
case exports.INPUT_TAG:
_this.extractInput(node, tag);
break;
case exports.UNION_TAG:
_this.extractUnion(node, tag);
break;
case exports.FIELD_TAG:
if (ts.isFunctionDeclaration(node)) {
_this.functionDeclarationExtendType(node, tag);
}
else if (!(ts.isParameter(node) ||
ts.isMethodDeclaration(node) ||
ts.isPropertyDeclaration(node) ||
ts.isMethodSignature(node) ||
ts.isPropertySignature(node))) {
// Right now this happens via deep traversal
// Note: Keep this in sync with `collectFields`
_this.reportUnhandled(node, "field", E.fieldTagOnWrongNode());
}
break;
case exports.KILLS_PARENT_ON_EXCEPTION_TAG: {
var hasFieldTag = ts.getJSDocTags(node).some(function (t) {
return t.tagName.text === exports.FIELD_TAG;
});
if (!hasFieldTag) {
_this.report(tag.tagName, E.killsParentOnExceptionOnWrongNode());
}
break;
}
default:
{
var lowerCaseTag = tag.tagName.text.toLowerCase();
if (lowerCaseTag.startsWith("gql")) {
try {
for (var ALL_TAGS_1 = __values(exports.ALL_TAGS), ALL_TAGS_1_1 = ALL_TAGS_1.next(); !ALL_TAGS_1_1.done; ALL_TAGS_1_1 = ALL_TAGS_1.next()) {
var t = ALL_TAGS_1_1.value;
if (t.toLowerCase() === lowerCaseTag) {
_this.report(tag.tagName, E.wrongCasingForGratsTag(tag.tagName.text, t));
break;
}
}
}
else if (!(ts.isMethodDeclaration(node) ||
ts.isPropertyDeclaration(node) ||
ts.isMethodSignature(node) ||
ts.isPropertySignature(node))) {
// Right now this happens via deep traversal
// Note: Keep this in sync with `collectFields`
_this.reportUnhandled(node, "`@".concat(FIELD_TAG, "` can only be used on method/property declarations or signatures."));
}
break;
case KILLS_PARENT_ON_EXCEPTION_TAG:
var hasFieldTag = ts.getJSDocTags(node).some(function (t) {
return t.tagName.text === FIELD_TAG;
});
if (!hasFieldTag) {
_this.report(tag.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "`. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` can only be used in field annotation docblocks. Perhaps you are missing a `@").concat(FIELD_TAG, "` tag?"));
}
break;
default:
var lowerCaseTag = tag.tagName.text.toLowerCase();
if (lowerCaseTag.startsWith("gql")) {
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
for (var ALL_TAGS_1 = (e_2 = void 0, __values(ALL_TAGS)), ALL_TAGS_1_1 = ALL_TAGS_1.next(); !ALL_TAGS_1_1.done; ALL_TAGS_1_1 = ALL_TAGS_1.next()) {
var t = ALL_TAGS_1_1.value;
if (t.toLowerCase() === lowerCaseTag) {
_this.report(tag.tagName, "Incorrect casing for Grats tag `@".concat(tag.tagName.text, "`. Use `@").concat(t, "` instead."));
break;
}
}
if (ALL_TAGS_1_1 && !ALL_TAGS_1_1.done && (_a = ALL_TAGS_1["return"])) _a.call(ALL_TAGS_1);
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (ALL_TAGS_1_1 && !ALL_TAGS_1_1.done && (_b = ALL_TAGS_1["return"])) _b.call(ALL_TAGS_1);
}
finally { if (e_2) throw e_2.error; }
}
_this.report(tag.tagName, "`@".concat(tag.tagName.text, "` is not a valid Grats tag. Valid tags are: ").concat(ALL_TAGS.map(function (t) { return "`@".concat(t, "`"); }).join(", "), "."));
finally { if (e_1) throw e_1.error; }
}
break;
_this.report(tag.tagName, E.invalidGratsTag(tag.tagName.text));
}
}
}
break;
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c);
}
finally { if (e_1) throw e_1.error; }
}
});

@@ -163,3 +177,3 @@ if (this.errors.length > 0) {

else {
this.report(tag, "`@".concat(TYPE_TAG, "` can only be used on class or interface declarations."));
this.report(tag, E.invalidTypeTagUsage());
}

@@ -172,3 +186,3 @@ };

else {
this.report(tag, "`@".concat(SCALAR_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidScalarTagUsage());
}

@@ -181,3 +195,3 @@ };

else {
this.report(tag, "`@".concat(INTERFACE_TAG, "` can only be used on interface declarations."));
this.report(tag, E.invalidInterfaceTagUsage());
}

@@ -193,3 +207,3 @@ };

else {
this.report(tag, "`@".concat(ENUM_TAG, "` can only be used on enum declarations or TypeScript unions."));
this.report(tag, E.invalidEnumTagUsage());
}

@@ -202,3 +216,3 @@ };

else {
this.report(tag, "`@".concat(INPUT_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidInputTagUsage());
}

@@ -211,7 +225,7 @@ };

else {
this.report(tag, "`@".concat(UNION_TAG, "` can only be used on type alias declarations."));
this.report(tag, E.invalidUnionTagUsage());
}
};
/** Error handling and location juggling */
Extractor.prototype.report = function (node, message) {
Extractor.prototype.report = function (node, message, relatedInformation) {
var start = node.getStart();

@@ -225,3 +239,4 @@ var length = node.getEnd() - start;

start: start,
length: length
length: length,
relatedInformation: relatedInformation
});

@@ -232,8 +247,17 @@ return null;

// Gives the user a path forward if they think we should be able to infer this type.
Extractor.prototype.reportUnhandled = function (node, message) {
var suggestion = "If you think ".concat(LIBRARY_NAME, " should be able to infer this type, please report an issue at ").concat(ISSUE_URL, ".");
Extractor.prototype.reportUnhandled = function (node, positionKind, message, relatedInformation) {
var suggestion = "If you think ".concat(exports.LIBRARY_NAME, " should be able to infer this ").concat(positionKind, ", please report an issue at ").concat(exports.ISSUE_URL, ".");
var completedMessage = "".concat(message, "\n\n").concat(suggestion);
this.report(node, completedMessage);
return null;
return this.report(node, completedMessage, relatedInformation);
};
Extractor.prototype.related = function (node, message) {
return {
category: ts.DiagnosticCategory.Message,
code: 0,
file: node.getSourceFile(),
start: node.getStart(),
length: node.getWidth(),
messageText: message
};
};
Extractor.prototype.diagnosticAnnotatedLocation = function (node) {

@@ -244,18 +268,5 @@ var start = node.getStart();

};
// TODO: This is potentially quite expensive, and we only need it if we report
// an error at one of these locations. We could consider some trick to return a
// proxy object that would lazily compute the line/column info.
Extractor.prototype.loc = function (node) {
var source = new graphql_1.Source(this.sourceFile.text, this.sourceFile.fileName);
var startToken = this.gqlDummyToken(node.getStart());
var endToken = this.gqlDummyToken(node.getEnd());
return new graphql_1.Location(startToken, endToken, source);
};
Extractor.prototype.gqlDummyToken = function (pos) {
var _a = this.sourceFile.getLineAndCharacterOfPosition(pos), line = _a.line, character = _a.character;
return new graphql_1.Token(graphql_1.TokenKind.SOF, pos, pos, line, character, undefined);
};
/** TypeScript traversals */
Extractor.prototype.unionTypeAliasDeclaration = function (node, tag) {
var e_3, _a;
var e_2, _a;
var name = this.entityName(node, tag);

@@ -265,3 +276,3 @@ if (name == null)

if (!ts.isUnionTypeNode(node.type)) {
return this.report(node, "Expected a TypeScript union. `@".concat(UNION_TAG, "` can only be used on TypeScript unions."));
return this.report(node, E.expectedUnionTypeNode());
}

@@ -274,5 +285,5 @@ var description = this.collectDescription(node.name);

if (!ts.isTypeReferenceNode(member)) {
return this.reportUnhandled(member, "Expected `@".concat(UNION_TAG, "` union members to be type references."));
return this.reportUnhandled(member, "union member", E.expectedUnionTypeReference());
}
var namedType = this.gqlNamedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
var namedType = this.gql.namedType(member.typeName, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(member.typeName, namedType.name);

@@ -282,3 +293,3 @@ types.push(namedType);

}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {

@@ -288,12 +299,6 @@ try {

}
finally { if (e_3) throw e_3.error; }
finally { if (e_2) throw e_2.error; }
}
this.ctx.recordTypeName(node.name, name.value);
this.definitions.push({
kind: graphql_1.Kind.UNION_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
name: name,
types: types
});
this.ctx.recordTypeName(node.name, name, "UNION");
this.definitions.push(this.gql.unionTypeDefinition(node, name, types, description));
};

@@ -306,3 +311,3 @@ Extractor.prototype.functionDeclarationExtendType = function (node, tag) {

if (typeParam == null) {
return this.report(funcName, "Expected `@".concat(FIELD_TAG, "` function to have a first argument representing the type to extend."));
return this.report(funcName, E.invalidParentArgForFunctionField());
}

@@ -316,3 +321,3 @@ var typeName = this.typeReferenceFromParam(typeParam);

if (node.type == null) {
return this.report(funcName, "Expected GraphQL field to have an explicit return type.");
return this.report(funcName, E.invalidReturnTypeForFunctionField());
}

@@ -327,11 +332,17 @@ var type = this.collectMethodType(node.type);

}
var context = node.parameters[2];
if (context != null) {
this.validateContextParameter(context);
}
var description = this.collectDescription(funcName);
if (!ts.isSourceFile(node.parent)) {
return this.report(node, "Expected `@".concat(FIELD_TAG, "` function to be a top-level declaration."));
return this.report(node, E.functionFieldNotTopLevel());
}
// TODO: Does this work in the browser?
var filename = this.ctx.getDestFilePath(node.parent);
var directives = [this.exportDirective(funcName, filename, funcName.text)];
var directives = [
this.exportDirective(funcName, filename, funcName.text),
];
if (funcName.text !== name.value) {
directives.push(this.methodNameDirective(funcName, funcName.text));
directives.push(this.fieldNameDirective(funcName, funcName.text));
}

@@ -342,28 +353,14 @@ var deprecated = this.collectDeprecated(node);

}
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION,
loc: this.loc(node),
name: typeName,
fields: [
{
kind: graphql_1.Kind.FIELD_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
arguments: args || undefined,
type: this.handleErrorBubbling(node, type),
directives: directives.length === 0 ? undefined : directives
},
]
});
var field = this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), args, directives, description);
this.definitions.push(this.gql.abstractFieldDefinition(node, typeName, field));
};
Extractor.prototype.typeReferenceFromParam = function (typeParam) {
if (typeParam.type == null) {
return this.report(typeParam, "Expected first argument of a `@".concat(FIELD_TAG, "` function to have an explicit type annotation."));
return this.report(typeParam, E.functionFieldParentTypeMissing());
}
if (!ts.isTypeReferenceNode(typeParam.type)) {
return this.report(typeParam.type, "Expected first argument of a `@".concat(FIELD_TAG, "` function to be typed as a `@").concat(TYPE_TAG, "` type."));
return this.report(typeParam.type, E.functionFieldParentTypeNotValid());
}
var nameNode = typeParam.type.typeName;
var typeName = this.gqlName(nameNode, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
var typeName = this.gql.name(nameNode, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(nameNode, typeName);

@@ -375,3 +372,3 @@ return typeName;

if (node.name == null) {
return this.report(node, "Expected a `@".concat(FIELD_TAG, "` function to be a named function."));
return this.report(node, E.functionFieldNotNamed());
}

@@ -386,6 +383,6 @@ var exportKeyword = (_a = node.modifiers) === null || _a === void 0 ? void 0 : _a.some(function (modifier) {

// TODO: We could support this
return this.report(defaultKeyword, "Expected a `@".concat(FIELD_TAG, "` function to be a named export, not a default export."));
return this.report(defaultKeyword, E.functionFieldDefaultExport());
}
if (exportKeyword == null) {
return this.report(node.name, "Expected a `@".concat(FIELD_TAG, "` function to be a named export."));
return this.report(node.name, E.functionFieldNotNamedExport());
}

@@ -399,9 +396,4 @@ return node.name;

var description = this.collectDescription(node.name);
this.ctx.recordTypeName(node.name, name.value);
this.definitions.push({
kind: graphql_1.Kind.SCALAR_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
name: name
});
this.ctx.recordTypeName(node.name, name, "SCALAR");
this.definitions.push(this.gql.scalarTypeDefinition(node, name, description));
};

@@ -413,19 +405,12 @@ Extractor.prototype.inputTypeAliasDeclaration = function (node, tag) {

var description = this.collectDescription(node.name);
this.ctx.recordTypeName(node.name, name.value);
this.ctx.recordTypeName(node.name, name, "INPUT_OBJECT");
var fields = this.collectInputFields(node);
var deprecatedDirective = this.collectDeprecated(node);
this.definitions.push({
kind: graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
name: name,
fields: fields !== null && fields !== void 0 ? fields : undefined,
directives: deprecatedDirective == null ? undefined : [deprecatedDirective]
});
this.definitions.push(this.gql.inputObjectTypeDefinition(node, name, fields, deprecatedDirective == null ? null : [deprecatedDirective], description));
};
Extractor.prototype.collectInputFields = function (node) {
var e_4, _a;
var e_3, _a;
var fields = [];
if (!ts.isTypeLiteralNode(node.type)) {
return this.reportUnhandled(node, "`@".concat(INPUT_TAG, "` can only be used on type literals."));
return this.reportUnhandled(node, "input", E.inputTypeNotLiteral());
}

@@ -436,3 +421,3 @@ try {

if (!ts.isPropertySignature(member)) {
this.reportUnhandled(member, "`@".concat(INPUT_TAG, "` types only support property signature members."));
this.reportUnhandled(member, "input field", E.inputTypeFieldNotProperty());
continue;

@@ -445,3 +430,3 @@ }

}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {

@@ -451,3 +436,3 @@ try {

}
finally { if (e_4) throw e_4.error; }
finally { if (e_3) throw e_3.error; }
}

@@ -461,3 +446,3 @@ return fields.length === 0 ? null : fields;

if (node.type == null) {
return this.report(node, "Input field must have a type annotation.");
return this.report(node, E.inputFieldUntyped());
}

@@ -467,18 +452,10 @@ var inner = this.collectType(node.type);

return null;
var type = node.questionToken == null ? inner : this.gqlNullableType(inner);
var type = node.questionToken == null ? inner : this.gql.nullableType(inner);
var description = this.collectDescription(node.name);
var deprecatedDirective = this.collectDeprecated(node);
return {
kind: graphql_1.Kind.INPUT_VALUE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
name: this.gqlName(id, id.text),
type: type,
defaultValue: undefined,
directives: deprecatedDirective == null ? undefined : [deprecatedDirective]
};
return this.gql.inputValueDefinition(node, this.gql.name(id, id.text), type, deprecatedDirective == null ? null : [deprecatedDirective], null, description);
};
Extractor.prototype.typeClassDeclaration = function (node, tag) {
if (node.name == null) {
return this.report(node, "Unexpected `@".concat(TYPE_TAG, "` annotation on unnamed class declaration."));
return this.report(node, E.typeTagOnUnamedClass());
}

@@ -491,13 +468,5 @@ var name = this.entityName(node, tag);

var interfaces = this.collectInterfaces(node);
this.ctx.recordTypeName(node.name, name.value);
this.ctx.recordTypeName(node.name, name, "TYPE");
this.checkForTypenameProperty(node, name.value);
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
directives: undefined,
name: name,
fields: fields,
interfaces: interfaces !== null && interfaces !== void 0 ? interfaces : undefined
});
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description));
};

@@ -511,13 +480,5 @@ Extractor.prototype.typeInterfaceDeclaration = function (node, tag) {

var interfaces = this.collectInterfaces(node);
this.ctx.recordTypeName(node.name, name.value);
this.ctx.recordTypeName(node.name, name, "INTERFACE");
this.checkForTypenameProperty(node, name.value);
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
directives: undefined,
name: name,
fields: fields,
interfaces: interfaces !== null && interfaces !== void 0 ? interfaces : undefined
});
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description));
};

@@ -529,3 +490,3 @@ Extractor.prototype.typeTypeAliasDeclaration = function (node, tag) {

if (!ts.isTypeLiteralNode(node.type)) {
this.reportUnhandled(node.type, "Expected `@".concat(TYPE_TAG, "` type to be a type literal. For example: `type Foo = { bar: string }`"));
this.reportUnhandled(node.type, "type", E.typeTagOnAliasOfNonObject());
return;

@@ -535,15 +496,6 @@ }

var fields = this.collectFields(node.type);
this.ctx.recordTypeName(node.name, name.value);
var interfaces = this.collectInterfaces(node);
this.ctx.recordTypeName(node.name, name, "TYPE");
this.checkForTypenameProperty(node.type, name.value);
this.definitions.push({
kind: graphql_1.Kind.OBJECT_TYPE_DEFINITION,
loc: this.loc(node),
description: description !== null && description !== void 0 ? description : undefined,
directives: undefined,
name: name,
fields: fields,
// I don't believe there is a reasonable way to specify that a type
// implements an interface.
interfaces: undefined
});
this.definitions.push(this.gql.objectTypeDefinition(node, name, fields, interfaces, description));
};

@@ -571,5 +523,4 @@ Extractor.prototype.checkForTypenameProperty = function (node, expectedName) {

}
this.report(member.name,
// TODO: Could show what kind we found, but TS AST does not have node names.
"Expected `__typename` to be a property declaration.");
this.report(member.name, E.typeNameNotDeclaration());
return false;

@@ -585,11 +536,11 @@ };

if (node.initializer == null) {
this.report(node.name, "Expected `__typename` property to have an initializer or a string literal type. For example: `__typename = \"MyType\"` or `__typename: \"MyType\";`.");
this.report(node.name, E.typeNameMissingInitializer());
return false;
}
if (!ts.isStringLiteral(node.initializer)) {
this.report(node.initializer, "Expected `__typename` property initializer to be a string literal. For example: `__typename = \"MyType\"` or `__typename: \"MyType\";`.");
this.report(node.initializer, E.typeNameInitializeNotString());
return false;
}
if (node.initializer.text !== expectedName) {
this.report(node.initializer, "Expected `__typename` property initializer to be `\"".concat(expectedName, "\"`, found `\"").concat(node.initializer.text, "\"`."));
this.report(node.initializer, E.typeNameInitializerWrong(expectedName, node.initializer.text));
return false;

@@ -601,3 +552,3 @@ }

if (node.type == null) {
this.report(node, "Expected `__typename` property signature to specify the typename as a string literal string type. For example `__typename: \"".concat(expectedName, "\";`"));
this.report(node, E.typeNameMissingTypeAnnotation(expectedName));
return false;

@@ -609,7 +560,7 @@ }

if (!ts.isLiteralTypeNode(node) || !ts.isStringLiteral(node.literal)) {
this.report(node, "Expected `__typename` property signature to specify the typename as a string literal string type. For example `__typename: \"".concat(expectedName, "\";`"));
this.report(node, E.typeNameTypeNotStringLiteral(expectedName));
return false;
}
if (node.literal.text !== expectedName) {
this.report(node, "Expected `__typename` property to be `\"".concat(expectedName, "\"`"));
this.report(node, E.typeNameDoesNotMatchExpected(expectedName));
return false;

@@ -620,15 +571,41 @@ }

Extractor.prototype.collectInterfaces = function (node) {
this.reportTagInterfaces(node);
return ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)
? this.collectHeritageInterfaces(node)
: null;
};
Extractor.prototype.reportTagInterfaces = function (node) {
var tag = this.findTag(node, exports.IMPLEMENTS_TAG_DEPRECATED);
if (tag == null)
return null;
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
this.report(tag, E.implementsTagOnClass());
}
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
this.report(tag, E.implementsTagOnInterface());
}
if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) {
this.report(tag, E.implementsTagOnTypeAlias());
}
};
Extractor.prototype.collectHeritageInterfaces = function (node) {
var _this = this;
if (node.heritageClauses == null)
return null;
var maybeInterfaces = node.heritageClauses.flatMap(function (clause) {
if (clause.token !== ts.SyntaxKind.ImplementsKeyword)
return [];
return clause.types.map(function (type) {
if (!ts.isIdentifier(type.expression)) {
// TODO: Are there valid cases we want to cover here?
return null;
}
var namedType = _this.gqlNamedType(type.expression, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
_this.ctx.markUnresolvedType(type.expression, namedType.name);
var maybeInterfaces = node.heritageClauses
.filter(function (clause) {
if (node.kind === ts.SyntaxKind.ClassDeclaration) {
return clause.token === ts.SyntaxKind.ImplementsKeyword;
}
// Interfaces can only have extends clauses, and those are allowed.
return true;
})
.flatMap(function (clause) {
return clause.types
.map(function (type) { return type.expression; })
.filter(function (expression) { return ts.isIdentifier(expression); })
.filter(function (expression) { return _this.symbolHasGqlTag(expression); })
.map(function (expression) {
var namedType = _this.gql.namedType(expression, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
_this.ctx.markUnresolvedType(expression, namedType.name);
return namedType;

@@ -643,3 +620,18 @@ });

};
Extractor.prototype.symbolHasGqlTag = function (node) {
var symbol = this.ctx.checker.getSymbolAtLocation(node);
if (symbol == null)
return false;
var declaration = this.ctx.findSymbolDeclaration(symbol);
if (declaration == null)
return false;
return this.hasGqlTag(declaration);
};
Extractor.prototype.hasGqlTag = function (node) {
return ts.getJSDocTags(node).some(function (tag) {
return exports.ALL_TAGS.includes(tag.tagName.text);
});
};
Extractor.prototype.interfaceInterfaceDeclaration = function (node, tag) {
var _this = this;
var name = this.entityName(node, tag);

@@ -649,16 +641,24 @@ if (name == null || name.value == null) {

}
// Prevent using merged interfaces as GraphQL interfaces.
// https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces
var symbol = this.ctx.checker.getSymbolAtLocation(node.name);
if (symbol != null &&
symbol.declarations != null &&
symbol.declarations.length > 1) {
var otherLocations = symbol.declarations
.filter(function (d) { return d !== node && ts.isInterfaceDeclaration(d); })
.map(function (d) {
var _a;
var locNode = (_a = ts.getNameOfDeclaration(d)) !== null && _a !== void 0 ? _a : d;
return _this.related(locNode, "Other declaration");
});
if (otherLocations.length > 0) {
return this.report(node.name, E.mergedInterfaces(name.value), otherLocations);
}
}
var description = this.collectDescription(node.name);
var interfaces = this.collectInterfaces(node);
var fields = this.collectFields(node);
this.ctx.recordTypeName(node.name, name.value);
// While GraphQL supports interfaces that extend other interfaces,
// TypeScript does not. So we can't support that here either.
// In the future we could support classes that act as interfaces through
// inheritance.
this.definitions.push({
kind: graphql_1.Kind.INTERFACE_TYPE_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
fields: fields
});
this.ctx.recordTypeName(node.name, name, "INTERFACE");
this.definitions.push(this.gql.interfaceTypeDefinition(node, name, fields, interfaces, description));
};

@@ -669,5 +669,26 @@ Extractor.prototype.collectFields = function (node) {

ts.forEachChild(node, function (node) {
var e_4, _a;
if (ts.isConstructorDeclaration(node)) {
try {
// Handle parameter properties
// https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties
for (var _b = __values(node.parameters), _c = _b.next(); !_c.done; _c = _b.next()) {
var param = _c.value;
var field = _this.constructorParam(param);
if (field != null) {
fields.push(field);
}
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b["return"])) _a.call(_b);
}
finally { if (e_4) throw e_4.error; }
}
}
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
var field = _this.methodDeclaration(node);
if (field) {
if (field != null) {
fields.push(field);

@@ -686,2 +707,52 @@ }

};
Extractor.prototype.constructorParam = function (node) {
var tag = this.findTag(node, exports.FIELD_TAG);
if (tag == null)
return null;
if (node.modifiers == null) {
return this.report(node, E.parameterWithoutModifiers());
}
var isParameterProperty = node.modifiers.some(function (modifier) {
return modifier.kind === ts.SyntaxKind.PublicKeyword ||
modifier.kind === ts.SyntaxKind.PrivateKeyword ||
modifier.kind === ts.SyntaxKind.ProtectedKeyword ||
modifier.kind === ts.SyntaxKind.ReadonlyKeyword;
});
if (!isParameterProperty) {
return this.report(node, E.parameterWithoutModifiers());
}
var notPublic = node.modifiers.find(function (modifier) {
return modifier.kind === ts.SyntaxKind.PrivateKeyword ||
modifier.kind === ts.SyntaxKind.ProtectedKeyword;
});
if (notPublic != null) {
return this.report(notPublic, E.parameterPropertyNotPublic());
}
var name = this.entityName(node, tag);
if (name == null)
return null;
if (node.type == null) {
return this.report(node, E.parameterPropertyMissingType());
}
var id = node.name;
if (ts.isArrayBindingPattern(id) || ts.isObjectBindingPattern(id)) {
// TypeScript triggers an error if a binding pattern is used for a
// parameter property, so we don't need to report them.
// https://www.typescriptlang.org/play?#code/MYGwhgzhAEBiD29oG8BQ1rHgOwgFwCcBXYPeAgCgAciAjEAS2BQDNEBfAShXdXaA
return null;
}
var directives = [];
if (id.text !== name.value) {
directives = [this.fieldNameDirective(node.name, id.text)];
}
var type = this.collectType(node.type);
if (type == null)
return null;
var deprecated = this.collectDeprecated(node);
if (deprecated != null) {
directives.push(deprecated);
}
var description = this.collectDescription(node.name);
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), null, directives, description);
};
Extractor.prototype.collectArgs = function (argsParam) {

@@ -692,9 +763,9 @@ var e_5, _a;

if (argsType == null) {
return this.report(argsParam, "Expected GraphQL field arguments to have a TypeScript type. If there are no arguments, you can use `args: never`.");
return this.report(argsParam, E.argumentParamIsMissingType());
}
if (argsType.kind === ts.SyntaxKind.NeverKeyword) {
if (argsType.kind === ts.SyntaxKind.UnknownKeyword) {
return [];
}
if (!ts.isTypeLiteralNode(argsType)) {
return this.report(argsType, "Expected GraphQL field arguments to be typed using a literal object: `{someField: string}`.");
return this.report(argsType, E.argumentParamIsNotObject());
}

@@ -747,19 +818,20 @@ var defaults = null;

if (ts.isStringLiteral(node)) {
return { kind: graphql_1.Kind.STRING, loc: this.loc(node), value: node.text };
return this.gql.string(node, node.text);
}
else if (ts.isNumericLiteral(node)) {
var kind = node.text.includes(".") ? graphql_1.Kind.FLOAT : graphql_1.Kind.INT;
return { kind: kind, loc: this.loc(node), value: node.text };
return node.text.includes(".")
? this.gql.float(node, node.text)
: this.gql.int(node, node.text);
}
else if (this.isNullish(node)) {
return { kind: graphql_1.Kind.NULL, loc: this.loc(node) };
return this.gql["null"](node);
}
else if (node.kind === ts.SyntaxKind.TrueKeyword) {
return { kind: graphql_1.Kind.BOOLEAN, loc: this.loc(node), value: true };
return this.gql.boolean(node, true);
}
else if (node.kind === ts.SyntaxKind.FalseKeyword) {
return { kind: graphql_1.Kind.BOOLEAN, loc: this.loc(node), value: false };
return this.gql.boolean(node, false);
}
else if (ts.isObjectLiteralExpression(node)) {
return this.cellectObjectLiteral(node);
return this.collectObjectLiteral(node);
}

@@ -769,4 +841,3 @@ else if (ts.isArrayLiteralExpression(node)) {

}
this.reportUnhandled(node, "Expected GraphQL field argument default values to be a literal.");
return null;
return this.reportUnhandled(node, "constant value", E.defaultValueIsNotLiteral());
};

@@ -799,9 +870,5 @@ Extractor.prototype.collectArrayLiteral = function (node) {

}
return {
kind: graphql_1.Kind.LIST,
loc: this.loc(node),
values: values
};
return this.gql.list(node, values);
};
Extractor.prototype.cellectObjectLiteral = function (node) {
Extractor.prototype.collectObjectLiteral = function (node) {
var e_8, _a;

@@ -832,14 +899,10 @@ var fields = [];

}
return {
kind: graphql_1.Kind.OBJECT,
loc: this.loc(node),
fields: fields
};
return this.gql.object(node, fields);
};
Extractor.prototype.collectObjectField = function (node) {
if (!ts.isPropertyAssignment(node)) {
return this.reportUnhandled(node, "Expected object literal property to be a property assignment.");
return this.reportUnhandled(node, "constant value", E.defaultArgElementIsNotAssignment());
}
if (node.name == null) {
return this.reportUnhandled(node, "Expected object literal property to have a name.");
return this.reportUnhandled(node, "field", E.defaultArgPropertyMissingName());
}

@@ -851,3 +914,3 @@ var name = this.expectIdentifier(node.name);

if (initialize == null) {
return this.report(node, "Expected object literal property to have an initializer. For example: `{ offset = 10}`.");
return this.report(node, E.defaultArgPropertyMissingInitializer());
}

@@ -857,8 +920,3 @@ var value = this.collectConstValue(initialize);

return null;
return {
kind: graphql_1.Kind.OBJECT_FIELD,
loc: this.loc(node),
name: this.gqlName(node.name, name.text),
value: value
};
return this.gql.constObjectField(node, this.gql.name(node.name, name.text), value);
};

@@ -868,10 +926,10 @@ Extractor.prototype.collectArg = function (node, defaults) {

// TODO: How can I create this error?
return this.report(node, "Expected GraphQL field argument type to be a property signature.");
return this.report(node, E.argIsNotProperty());
}
if (!ts.isIdentifier(node.name)) {
// TODO: How can I create this error?
return this.report(node.name, "Expected GraphQL field argument names to be a literal.");
return this.report(node.name, E.argNameNotLiteral());
}
if (node.type == null) {
return this.report(node.name, "Expected GraphQL field argument to have a type.");
return this.report(node.name, E.argNotTyped());
}

@@ -882,3 +940,9 @@ var type = this.collectType(node.type);

if (node.questionToken) {
type = this.gqlNullableType(type);
/*
// TODO: Don't allow args that are optional but don't accept null
if (type.kind === Kind.NON_NULL_TYPE) {
return this.report(node.questionToken, E.nonNullTypeCannotBeOptional());
}
*/
type = this.gql.nullableType(type);
}

@@ -894,11 +958,3 @@ var description = this.collectDescription(node.name);

var deprecatedDirective = this.collectDeprecated(node);
return {
kind: graphql_1.Kind.INPUT_VALUE_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: this.gqlName(node.name, node.name.text),
type: type,
defaultValue: defaultValue || undefined,
directives: deprecatedDirective != null ? [deprecatedDirective] : undefined
};
return this.gql.inputValueDefinition(node, this.gql.name(node.name, node.name.text), type, deprecatedDirective == null ? null : [deprecatedDirective], defaultValue, description);
};

@@ -912,10 +968,4 @@ Extractor.prototype.enumEnumDeclaration = function (node, tag) {

var values = this.collectEnumValues(node);
this.ctx.recordTypeName(node.name, name.value);
this.definitions.push({
kind: graphql_1.Kind.ENUM_TYPE_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
values: values
});
this.ctx.recordTypeName(node.name, name, "ENUM");
this.definitions.push(this.gql.enumTypeDefinition(node, name, values, description));
};

@@ -931,10 +981,4 @@ Extractor.prototype.enumTypeAliasDeclaration = function (node, tag) {

var description = this.collectDescription(node.name);
this.ctx.recordTypeName(node.name, name.value);
this.definitions.push({
kind: graphql_1.Kind.ENUM_TYPE_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
values: values
});
this.ctx.recordTypeName(node.name, name, "ENUM");
this.definitions.push(this.gql.enumTypeDefinition(node, name, values, description));
};

@@ -952,12 +996,7 @@ Extractor.prototype.enumTypeAliasVariants = function (node) {

return [
{
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION,
name: this.gqlName(node.type.literal, node.type.literal.text),
description: undefined,
loc: this.loc(node)
},
this.gql.enumValueDefinition(node, this.gql.name(node.type.literal, node.type.literal.text), undefined, null),
];
}
if (!ts.isUnionTypeNode(node.type)) {
this.reportUnhandled(node.type, "Expected `@".concat(ENUM_TAG, "` to be a union type, or a string literal in the edge case of a single value enum."));
this.reportUnhandled(node.type, "union", E.enumTagOnInvalidNode());
return null;

@@ -974,2 +1013,3 @@ }

if (((_b = symbol === null || symbol === void 0 ? void 0 : symbol.declarations) === null || _b === void 0 ? void 0 : _b.length) === 1) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
var declaration = symbol.declarations[0];

@@ -981,9 +1021,3 @@ if (ts.isTypeAliasDeclaration(declaration)) {

var memberDescription = this.collectDescription(declaration.name);
values.push({
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION,
name: this.gqlName(declaration.type.literal, declaration.type.literal.text),
directives: deprecatedDirective ? [deprecatedDirective] : [],
description: memberDescription || undefined,
loc: this.loc(node)
});
values.push(this.gql.enumValueDefinition(node, this.gql.name(declaration.type.literal, declaration.type.literal.text), deprecatedDirective ? [deprecatedDirective] : [], memberDescription));
continue;

@@ -997,3 +1031,3 @@ }

!ts.isStringLiteral(member.literal)) {
this.reportUnhandled(member, "Expected `@".concat(ENUM_TAG, "` enum members to be string literal types. For example: `'foo'`."));
this.reportUnhandled(member, "union member", E.enumVariantNotStringLiteral());
continue;

@@ -1003,8 +1037,3 @@ }

// does not allow comments attached to string literal types.
values.push({
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION,
name: this.gqlName(member.literal, member.literal.text),
description: undefined,
loc: this.loc(member)
});
values.push(this.gql.enumValueDefinition(node, this.gql.name(member.literal, member.literal.text), undefined, null));
}

@@ -1029,3 +1058,3 @@ }

!ts.isStringLiteral(member.initializer)) {
this.reportUnhandled(member, "Expected `@".concat(ENUM_TAG, "` enum members to have string literal initializers. For example: `FOO = 'foo'`."));
this.reportUnhandled(member, "enum value", E.enumVariantMissingInitializer());
continue;

@@ -1035,9 +1064,3 @@ }

var deprecated = this.collectDeprecated(member);
values.push({
kind: graphql_1.Kind.ENUM_VALUE_DEFINITION,
loc: this.loc(member),
description: description || undefined,
name: this.gqlName(member.initializer, member.initializer.text),
directives: deprecated ? [deprecated] : undefined
});
values.push(this.gql.enumValueDefinition(member, this.gql.name(member.initializer, member.initializer.text), deprecated ? [deprecated] : undefined, description));
}

@@ -1059,7 +1082,7 @@ }

// FIXME: Use the _value_'s location not the tag's
return this.gqlName(tag, commentName);
return this.gql.name(tag, commentName);
}
}
if (node.name == null) {
return this.report(node, "Expected GraphQL entity to have a name.");
return this.report(node, E.gqlEntityMissingName());
}

@@ -1069,6 +1092,45 @@ var id = this.expectIdentifier(node.name);

return null;
return this.gqlName(id, id.text);
return this.gql.name(id, id.text);
};
// Ensure the type of the ctx param resolves to the declaration
// annotated with `@gqlContext`.
Extractor.prototype.validateContextParameter = function (node) {
if (node.type == null) {
return this.report(node, E.expectedTypeAnnotationOnContext());
}
if (node.type.kind === ts.SyntaxKind.UnknownKeyword) {
// If the user just needs to define the argument to get to a later parameter,
// they can use `ctx: unknown` to safely avoid triggering a Grats error.
return;
}
if (!ts.isTypeReferenceNode(node.type)) {
return this.report(node.type, E.expectedTypeAnnotationOfReferenceOnContext());
}
// Check for ...
if (node.dotDotDotToken != null) {
return this.report(node.dotDotDotToken, E.unexpectedParamSpreadForContextParam());
}
var symbol = this.ctx.checker.getSymbolAtLocation(node.type.typeName);
if (symbol == null) {
return this.report(node.type.typeName, E.expectedTypeAnnotationOnContextToBeResolvable());
}
var declaration = this.ctx.findSymbolDeclaration(symbol);
if (declaration == null) {
return this.report(node.type.typeName, E.expectedTypeAnnotationOnContextToHaveDeclaration());
}
if (this.ctx.gqlContext == null) {
// This is the first typed context value we've seen...
this.ctx.gqlContext = {
declaration: declaration,
firstReference: node.type.typeName
};
}
else if (this.ctx.gqlContext.declaration !== declaration) {
return this.report(node.type.typeName, E.multipleContextTypes(), [
this.related(this.ctx.gqlContext.firstReference, "A different type reference was used here"),
]);
}
};
Extractor.prototype.methodDeclaration = function (node) {
var tag = this.findTag(node, FIELD_TAG);
var tag = this.findTag(node, exports.FIELD_TAG);
if (tag == null)

@@ -1080,3 +1142,3 @@ return null;

if (node.type == null) {
return this.report(node.name, "Expected GraphQL field to have a type.");
return this.report(node.name, E.methodMissingType());
}

@@ -1092,2 +1154,6 @@ var type = this.collectMethodType(node.type);

}
var context = node.parameters[1];
if (context != null) {
this.validateContextParameter(context);
}
var description = this.collectDescription(node.name);

@@ -1099,3 +1165,3 @@ var id = this.expectIdentifier(node.name);

if (id.text !== name.value) {
directives = [this.methodNameDirective(node.name, id.text)];
directives = [this.fieldNameDirective(node.name, id.text)];
}

@@ -1106,11 +1172,3 @@ var deprecated = this.collectDeprecated(node);

}
return {
kind: graphql_1.Kind.FIELD_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
arguments: args || undefined,
type: this.handleErrorBubbling(node, type),
directives: directives.length === 0 ? undefined : directives
};
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), args, directives, description);
};

@@ -1137,3 +1195,3 @@ Extractor.prototype.collectMethodType = function (node) {

if (node.typeArguments == null) {
return this.report(node, "Expected type reference to have type arguments.");
return this.report(node, E.promiseMissingTypeArg());
}

@@ -1148,3 +1206,3 @@ return node.typeArguments[0];

if (symbol == null) {
return this.report(node, "Expected TypeScript to be able to resolve this GraphQL entity to a symbol.");
return this.report(node, E.cannotResolveSymbolForDescription());
}

@@ -1154,8 +1212,3 @@ var doc = symbol.getDocumentationComment(this.ctx.checker);

if (description) {
return {
kind: graphql_1.Kind.STRING,
loc: this.loc(node),
value: description,
block: true
};
return this.gql.string(node, description, true);
}

@@ -1173,15 +1226,9 @@ return null;

// FIXME: Use the _value_'s location not the tag's
reason = this.gqlConstArgument(tag, this.gqlName(tag, "reason"), this.gqlString(tag, reasonComment));
reason = this.gql.constArgument(tag, this.gql.name(tag, "reason"), this.gql.string(tag, reasonComment));
}
}
var args = reason == null ? undefined : [reason];
return {
kind: graphql_1.Kind.DIRECTIVE,
loc: this.loc(tag),
name: this.gqlName(tag, DEPRECATED_TAG),
arguments: args
};
return this.gql.constDirective(tag.tagName, this.gql.name(node, DEPRECATED_TAG), reason == null ? null : [reason]);
};
Extractor.prototype.property = function (node) {
var tag = this.findTag(node, FIELD_TAG);
var tag = this.findTag(node, exports.FIELD_TAG);
if (tag == null)

@@ -1193,3 +1240,3 @@ return null;

if (node.type == null) {
this.report(node.name, "Expected GraphQL field to have a type.");
this.report(node.name, E.propertyFieldMissingType());
return null;

@@ -1201,3 +1248,3 @@ }

return null;
var type = node.questionToken == null ? inner : this.gqlNullableType(inner);
var type = node.questionToken == null ? inner : this.gql.nullableType(inner);
var description = this.collectDescription(node.name);

@@ -1213,14 +1260,8 @@ var directives = [];

if (id.text !== name.value) {
directives = [this.methodNameDirective(node.name, id.text)];
directives = [this.fieldNameDirective(node.name, id.text)];
}
return {
kind: graphql_1.Kind.FIELD_DEFINITION,
loc: this.loc(node),
description: description || undefined,
name: name,
arguments: undefined,
type: this.handleErrorBubbling(node, type),
directives: directives.length === 0 ? undefined : directives
};
return this.gql.fieldDefinition(node, name, this.handleErrorBubbling(node, type), null, directives, description);
};
// TODO: Support separate modes for input and output types
// For input nodes and field may only be optional if `null` is a valid value.
Extractor.prototype.collectType = function (node) {

@@ -1238,9 +1279,8 @@ var _this = this;

return null;
return this.gqlNonNullType(node, this.gqlListType(node, element));
return this.gql.nonNullType(node, this.gql.listType(node, element));
}
else if (ts.isUnionTypeNode(node)) {
var types = node.types.filter(function (type) { return !_this.isNullish(type); });
if (types.length !== 1) {
this.report(node, "Expected exactly one non-nullish type.");
return null;
if (types.length === 0) {
return this.report(node, E.expectedOneNonNullishType());
}

@@ -1250,6 +1290,15 @@ var type = this.collectType(types[0]);

return null;
if (types.length > 1) {
var _a = __read(types), first = _a[0], rest = _a.slice(1);
// FIXME: If each of `rest` matches `first` this should be okay.
var incompatibleVariants = rest.map(function (tsType) {
return _this.related(tsType, "Other non-nullish type");
});
this.report(first, E.expectedOneNonNullishType(), incompatibleVariants);
return null;
}
if (node.types.length > 1) {
return this.gqlNullableType(type);
return this.gql.nullableType(type);
}
return this.gqlNonNullType(node, type);
return this.gql.nonNullType(node, type);
}

@@ -1260,15 +1309,15 @@ else if (ts.isParenthesizedTypeNode(node)) {

else if (node.kind === ts.SyntaxKind.StringKeyword) {
return this.gqlNonNullType(node, this.gqlNamedType(node, "String"));
return this.gql.nonNullType(node, this.gql.namedType(node, "String"));
}
else if (node.kind === ts.SyntaxKind.BooleanKeyword) {
return this.gqlNonNullType(node, this.gqlNamedType(node, "Boolean"));
return this.gql.nonNullType(node, this.gql.namedType(node, "Boolean"));
}
else if (node.kind === ts.SyntaxKind.NumberKeyword) {
return this.report(node, "Unexpected number type. GraphQL supports both Int and Float, making `number` ambiguous. Instead, import the `Int` or `Float` type from `".concat(LIBRARY_IMPORT_NAME, "` and use that. e.g. `import { Int, Float } from \"").concat(LIBRARY_IMPORT_NAME, "\";`."));
return this.report(node, E.ambiguousNumberType());
}
else if (ts.isTypeLiteralNode(node)) {
return this.report(node, "Unexpected type literal. You may want to define a named GraphQL type elsewhere and reference it here.");
return this.report(node, E.unsupportedTypeLiteral());
}
// TODO: Better error message. This is okay if it's a type reference, but everything else is not.
this.reportUnhandled(node, "Unknown GraphQL type.");
this.reportUnhandled(node, "type", E.unknownGraphQLType());
return null;

@@ -1286,3 +1335,3 @@ };

if (node.typeArguments == null) {
return this.report(node, "Expected type reference to have type arguments.");
return this.report(node, E.pluralTypeMissingParameter());
}

@@ -1292,3 +1341,3 @@ var element = this.collectType(node.typeArguments[0]);

return null;
return this.gqlNonNullType(node, this.gqlListType(node, element));
return this.gql.nonNullType(node, this.gql.listType(node, element));
}

@@ -1300,5 +1349,5 @@ default: {

// A later pass will resolve the type.
var namedType = this.gqlNamedType(node, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
var namedType = this.gql.namedType(node, TypeContext_1.UNRESOLVED_REFERENCE_NAME);
this.ctx.markUnresolvedType(node.typeName, namedType.name);
return this.gqlNonNullType(node, namedType);
return this.gql.nonNullType(node, namedType);
}

@@ -1323,9 +1372,22 @@ }

}
return this.report(node, "Expected an identifier.");
return this.report(node, E.expectedIdentifier());
};
Extractor.prototype.findTag = function (node, tagName) {
var _a;
return ((_a = ts
var _this = this;
var tags = ts
.getJSDocTags(node)
.find(function (tag) { return tag.tagName.escapedText === tagName; })) !== null && _a !== void 0 ? _a : null);
.filter(function (tag) { return tag.tagName.escapedText === tagName; });
if (tags.length === 0) {
return null;
}
if (tags.length > 1) {
var additionalTags = tags.slice(1).map(function (tag) {
return _this.related(tag, "Additional tag");
});
var message = tagName === exports.IMPLEMENTS_TAG_DEPRECATED
? E.duplicateInterfaceTag()
: E.duplicateTag(tagName);
return this.report(tags[0], message, additionalTags);
}
return tags[0];
};

@@ -1338,9 +1400,9 @@ // It is a GraphQL best practice to model all fields as nullable. This allows

var tags = ts.getJSDocTags(parentNode);
var killsParentOnExceptions = tags.find(function (tag) { return tag.tagName.text === KILLS_PARENT_ON_EXCEPTION_TAG; });
var killsParentOnExceptions = tags.find(function (tag) { return tag.tagName.text === exports.KILLS_PARENT_ON_EXCEPTION_TAG; });
if (killsParentOnExceptions) {
if (!this.configOptions.nullableByDefault) {
this.report(killsParentOnExceptions.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` tag. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` is only supported when the Grats config `nullableByDefault` is enabled."));
this.report(killsParentOnExceptions.tagName, E.killsParentOnExceptionWithWrongConfig());
}
if (type.kind !== graphql_1.Kind.NON_NULL_TYPE) {
this.report(killsParentOnExceptions.tagName, "Unexpected `@".concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` tag. `@").concat(KILLS_PARENT_ON_EXCEPTION_TAG, "` is unnessesary on fields that are already nullable."));
this.report(killsParentOnExceptions.tagName, E.killsParentOnExceptionOnNullable());
}

@@ -1350,55 +1412,20 @@ return type;

if (this.configOptions.nullableByDefault) {
return this.gqlNullableType(type);
return this.gql.nullableType(type);
}
return type;
};
Extractor.prototype.methodNameDirective = function (nameNode, name) {
return this.gqlConstDirective(nameNode, this.gqlName(nameNode, serverDirectives_1.METHOD_NAME_DIRECTIVE), [
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.METHOD_NAME_ARG), this.gqlString(nameNode, name)),
/* Grats directives */
Extractor.prototype.exportDirective = function (nameNode, filename, functionName) {
return this.gql.constDirective(nameNode, this.gql.name(nameNode, serverDirectives_1.EXPORTED_DIRECTIVE), [
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.EXPORTED_FILENAME_ARG), this.gql.string(nameNode, filename)),
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.EXPORTED_FUNCTION_NAME_ARG), this.gql.string(nameNode, functionName)),
]);
};
Extractor.prototype.exportDirective = function (nameNode, filename, functionName) {
return this.gqlConstDirective(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_DIRECTIVE), [
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FILENAME_ARG), this.gqlString(nameNode, filename)),
this.gqlConstArgument(nameNode, this.gqlName(nameNode, serverDirectives_1.EXPORTED_FUNCTION_NAME_ARG), this.gqlString(nameNode, functionName)),
Extractor.prototype.fieldNameDirective = function (nameNode, name) {
return this.gql.constDirective(nameNode, this.gql.name(nameNode, serverDirectives_1.METHOD_NAME_DIRECTIVE), [
this.gql.constArgument(nameNode, this.gql.name(nameNode, serverDirectives_1.METHOD_NAME_ARG), this.gql.string(nameNode, name)),
]);
};
/** GraphQL AST node helper methods */
Extractor.prototype.gqlName = function (node, value) {
return { kind: graphql_1.Kind.NAME, loc: this.loc(node), value: value };
};
Extractor.prototype.gqlNamedType = function (node, value) {
return {
kind: graphql_1.Kind.NAMED_TYPE,
loc: this.loc(node),
name: this.gqlName(node, value)
};
};
Extractor.prototype.gqlNonNullType = function (node, type) {
if (type.kind === graphql_1.Kind.NON_NULL_TYPE) {
return type;
}
return { kind: graphql_1.Kind.NON_NULL_TYPE, loc: this.loc(node), type: type };
};
Extractor.prototype.gqlNullableType = function (type) {
var inner = type;
while (inner.kind === graphql_1.Kind.NON_NULL_TYPE) {
inner = inner.type;
}
return inner;
};
Extractor.prototype.gqlListType = function (node, type) {
return { kind: graphql_1.Kind.LIST_TYPE, loc: this.loc(node), type: type };
};
Extractor.prototype.gqlConstArgument = function (node, name, value) {
return { kind: graphql_1.Kind.ARGUMENT, loc: this.loc(node), name: name, value: value };
};
Extractor.prototype.gqlConstDirective = function (node, name, args) {
return { kind: graphql_1.Kind.DIRECTIVE, loc: this.loc(node), name: name, arguments: args };
};
Extractor.prototype.gqlString = function (node, value) {
return { kind: graphql_1.Kind.STRING, loc: this.loc(node), value: value };
};
return Extractor;
}());
exports.Extractor = Extractor;

@@ -44,2 +44,3 @@ "use strict";

var serverDirectives_1 = require("./serverDirectives");
var helpers_1 = require("./utils/helpers");
var serverDirectives_2 = require("./serverDirectives");

@@ -91,2 +92,3 @@ __createBinding(exports, serverDirectives_2, "applyServerDirectives");

var definitions = Array.from(serverDirectives_1.DIRECTIVES_AST.definitions);
var errors = [];
try {

@@ -103,3 +105,4 @@ for (var _c = __values(program.getSourceFiles()), _d = _c.next(); !_d.done; _d = _c.next()) {

if (typeErrors.length > 0) {
return (0, DiagnosticError_1.err)(typeErrors);
(0, helpers_1.extend)(errors, typeErrors);
continue;
}

@@ -114,3 +117,4 @@ }

// the first one.
return (0, DiagnosticError_1.err)([syntaxErrors[0]]);
errors.push(syntaxErrors[0]);
continue;
}

@@ -120,4 +124,6 @@ }

var extractedResult = extractor.extract();
if (extractedResult.kind === "ERROR")
return extractedResult;
if (extractedResult.kind === "ERROR") {
(0, helpers_1.extend)(errors, extractedResult.err);
continue;
}
try {

@@ -145,3 +151,16 @@ for (var _e = (e_2 = void 0, __values(extractedResult.value)), _f = _e.next(); !_f.done; _f = _e.next()) {

}
var docResult = ctx.resolveTypes({ kind: graphql_1.Kind.DOCUMENT, definitions: definitions });
if (errors.length > 0) {
return (0, DiagnosticError_1.err)(errors);
}
// If you define a field on an interface using the functional style, we need to add
// that field to each concrete type as well. This must be done after all types are created,
// but before we validate the schema.
var definitionsResult = ctx.handleAbstractDefinitions(definitions);
if (definitionsResult.kind === "ERROR") {
return definitionsResult;
}
var docResult = ctx.resolveTypes({
kind: graphql_1.Kind.DOCUMENT,
definitions: definitionsResult.value
});
if (docResult.kind === "ERROR")

@@ -148,0 +167,0 @@ return docResult;

@@ -54,2 +54,3 @@ "use strict";

var gratsRoot_1 = require("./gratsRoot");
// TODO: Rename to be generic since it can apply to properties as well as methods.
exports.METHOD_NAME_DIRECTIVE = "methodName";

@@ -56,0 +57,0 @@ exports.METHOD_NAME_ARG = "name";

@@ -65,26 +65,36 @@ "use strict";

var utils_1 = require("@graphql-tools/utils");
function main() {
return __awaiter(this, void 0, void 0, function () {
var write, filter, filterRegex, failures, testDirs_1, testDirs_1_1, _a, fixturesDir_1, transformer, runner, e_1_1;
var e_1, _b;
return __generator(this, function (_c) {
switch (_c.label) {
var ts = require("typescript");
var graphql_1 = require("graphql");
var commander_1 = require("commander");
var Locate_1 = require("../Locate");
var DiagnosticError_1 = require("../utils/DiagnosticError");
var program = new commander_1.Command();
program
.name("grats-tests")
.description("Run Grats' internal tests")
.option("-w, --write", "Write the actual output of the test to the expected output files. Useful for updating tests.")
.option("-f, --filter <FILTER_REGEX>", "A regex to filter the tests to run. Only tests with a file path matching the regex will be run.")
.action(function (_a) {
var filter = _a.filter, write = _a.write;
return __awaiter(void 0, void 0, void 0, function () {
var filterRegex, failures, testDirs_1, testDirs_1_1, _b, fixturesDir_1, transformer, runner, e_1_1;
var e_1, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
write = process.argv.some(function (arg) { return arg === "--write"; });
filter = process.argv.find(function (arg) { return arg.startsWith("--filter="); });
filterRegex = filter != null ? filter.slice(9) : null;
filterRegex = filter !== null && filter !== void 0 ? filter : null;
failures = false;
_c.label = 1;
_d.label = 1;
case 1:
_c.trys.push([1, 6, 7, 8]);
_d.trys.push([1, 6, 7, 8]);
testDirs_1 = __values(testDirs), testDirs_1_1 = testDirs_1.next();
_c.label = 2;
_d.label = 2;
case 2:
if (!!testDirs_1_1.done) return [3 /*break*/, 5];
_a = testDirs_1_1.value, fixturesDir_1 = _a.fixturesDir, transformer = _a.transformer;
runner = new TestRunner_1["default"](fixturesDir_1, write, filterRegex, transformer);
_b = testDirs_1_1.value, fixturesDir_1 = _b.fixturesDir, transformer = _b.transformer;
runner = new TestRunner_1["default"](fixturesDir_1, !!write, filterRegex, transformer);
return [4 /*yield*/, runner.run()];
case 3:
failures = !(_c.sent()) || failures;
_c.label = 4;
failures = !(_d.sent()) || failures;
_d.label = 4;
case 4:

@@ -95,3 +105,3 @@ testDirs_1_1 = testDirs_1.next();

case 6:
e_1_1 = _c.sent();
e_1_1 = _d.sent();
e_1 = { error: e_1_1 };

@@ -101,3 +111,3 @@ return [3 /*break*/, 8];

try {
if (testDirs_1_1 && !testDirs_1_1.done && (_b = testDirs_1["return"])) _b.call(testDirs_1);
if (testDirs_1_1 && !testDirs_1_1.done && (_c = testDirs_1["return"])) _c.call(testDirs_1);
}

@@ -114,4 +124,5 @@ finally { if (e_1) throw e_1.error; }

});
}
});
var fixturesDir = path.join(__dirname, "fixtures");
var integrationFixturesDir = path.join(__dirname, "integrationFixtures");
var testDirs = [

@@ -139,12 +150,76 @@ {

};
var schemaResult = (0, lib_1.buildSchemaResult)(parsedOptions);
// https://stackoverflow.com/a/66604532/1263117
var compilerHost = ts.createCompilerHost(parsedOptions.options,
/* setParentNodes this is needed for finding jsDocs */
true);
var schemaResult = (0, lib_1.buildSchemaResultWithHost)(parsedOptions, compilerHost);
if (schemaResult.kind === "ERROR") {
return schemaResult.err.formatDiagnosticsWithContext();
}
return (0, utils_1.printSchemaWithDirectives)(schemaResult.value, {
assumeValid: true
});
var LOCATION_REGEX = /^\/\/ Locate: (.*)/;
var locationMatch = code.match(LOCATION_REGEX);
if (locationMatch != null) {
var locResult = (0, Locate_1.locate)(schemaResult.value, locationMatch[1].trim());
if (locResult.kind === "ERROR") {
return locResult.err;
}
return new DiagnosticError_1.ReportableDiagnostics(compilerHost, [
(0, DiagnosticError_1.diagnosticAtGraphQLLocation)("Located here", locResult.value),
]).formatDiagnosticsWithContext();
}
else {
return (0, utils_1.printSchemaWithDirectives)(schemaResult.value, {
assumeValid: true
});
}
}
},
{
fixturesDir: integrationFixturesDir,
transformer: function (code, fileName) { return __awaiter(void 0, void 0, void 0, function () {
var filePath, server, options, files, parsedOptions, schemaResult, schema, data;
return __generator(this, function (_a) {
var _b;
switch (_a.label) {
case 0:
filePath = "".concat(integrationFixturesDir, "/").concat(fileName);
return [4 /*yield*/, (_b = filePath, Promise.resolve().then(function () { return require(_b); }))];
case 1:
server = _a.sent();
if (server.query == null || typeof server.query !== "string") {
throw new Error("Expected `".concat(filePath, "` to export a query text as `query`"));
}
if (server.Query == null || typeof server.Query !== "function") {
throw new Error("Expected `".concat(filePath, "` to export a Query type as `Query`"));
}
options = {
nullableByDefault: true
};
files = [filePath, "src/Types.ts"];
parsedOptions = {
options: {},
raw: {
grats: options
},
errors: [],
fileNames: files
};
schemaResult = (0, lib_1.buildSchemaResult)(parsedOptions);
if (schemaResult.kind === "ERROR") {
throw new Error(schemaResult.err.formatDiagnosticsWithContext());
}
schema = schemaResult.value;
return [4 /*yield*/, (0, graphql_1.graphql)({
schema: schema,
source: server.query,
rootValue: new server.Query()
})];
case 2:
data = _a.sent();
return [2 /*return*/, JSON.stringify(data, null, 2)];
}
});
}); }
},
];
main();
program.parse();

@@ -74,3 +74,4 @@ "use strict";

this._testFixtures.push(fileName);
if (filterRegex != null && !fileName.match(filterRegex)) {
var filePath = path.join(fixturesDir, fileName);
if (filterRegex != null && !filePath.match(filterRegex)) {
this._skip.add(fileName);

@@ -77,0 +78,0 @@ }

@@ -1,6 +0,26 @@

import { DocumentNode, NameNode } from "graphql";
import { DefinitionNode, DocumentNode, FieldDefinitionNode, Location, NameNode } from "graphql";
import * as ts from "typescript";
import { DiagnosticResult, DiagnosticsResult } from "./utils/DiagnosticError";
import { InterfaceMap } from "./InterfaceGraph";
export declare const UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__";
type NameDefinition = {
name: NameNode;
kind: "TYPE" | "INTERFACE" | "UNION" | "SCALAR" | "INPUT_OBJECT" | "ENUM";
};
export type GratsDefinitionNode = DefinitionNode | AbstractFieldDefinitionNode;
export type AbstractFieldDefinitionNode = {
readonly kind: "AbstractFieldDefinition";
readonly loc: Location;
readonly onType: NameNode;
readonly field: FieldDefinitionNode;
};
/**
* Information about the GraphQL context type. We track the first value we see,
* and then validate that any other values we see are the same.
*/
type GqlContext = {
declaration: ts.Node;
firstReference: ts.Node;
};
/**
* Used to track TypeScript references.

@@ -21,13 +41,21 @@ *

_options: ts.ParsedCommandLine;
_symbolToName: Map<ts.Symbol, string>;
_symbolToName: Map<ts.Symbol, NameDefinition>;
_unresolvedTypes: Map<NameNode, ts.Symbol>;
gqlContext: GqlContext | null;
hasTypename: Set<string>;
constructor(options: ts.ParsedCommandLine, checker: ts.TypeChecker, host: ts.CompilerHost);
recordTypeName(node: ts.Node, name: string): void;
recordTypeName(node: ts.Node, name: NameNode, kind: NameDefinition["kind"]): void;
recordHasTypenameField(name: string): void;
markUnresolvedType(node: ts.Node, name: NameNode): void;
findSymbolDeclaration(startSymbol: ts.Symbol): ts.Declaration | null;
resolveSymbol(startSymbol: ts.Symbol): ts.Symbol;
resolveTypes(doc: DocumentNode): DiagnosticsResult<DocumentNode>;
handleAbstractDefinitions(docs: GratsDefinitionNode[]): DiagnosticsResult<DefinitionNode[]>;
addAbstractFieldDefinition(doc: AbstractFieldDefinitionNode, interfaceGraph: InterfaceMap): DiagnosticsResult<DefinitionNode[]>;
resolveNamedType(unresolved: NameNode): DiagnosticResult<NameNode>;
err(loc: Location, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): ts.Diagnostic;
relatedInformation(loc: Location, message: string): ts.DiagnosticRelatedInformation;
validateInterfaceImplementorsHaveTypenameField(): DiagnosticResult<null>;
getDestFilePath(sourceFile: ts.SourceFile): string;
}
export {};

@@ -13,2 +13,13 @@ "use strict";

};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
exports.__esModule = true;

@@ -20,2 +31,7 @@ exports.TypeContext = exports.UNRESOLVED_REFERENCE_NAME = void 0;

var gratsRoot_1 = require("./gratsRoot");
var serverDirectives_1 = require("./serverDirectives");
var Extractor_1 = require("./Extractor");
var E = require("./Errors");
var InterfaceGraph_1 = require("./InterfaceGraph");
var helpers_1 = require("./utils/helpers");
exports.UNRESOLVED_REFERENCE_NAME = "__UNRESOLVED_REFERENCE__";

@@ -38,2 +54,5 @@ /**

this._unresolvedTypes = new Map();
// The resolver context declaration, if it has been encountered.
// Gets mutated by Extractor.
this.gqlContext = null;
this.hasTypename = new Set();

@@ -44,3 +63,3 @@ this._options = options;

}
TypeContext.prototype.recordTypeName = function (node, name) {
TypeContext.prototype.recordTypeName = function (node, name, kind) {
var symbol = this.checker.getSymbolAtLocation(node);

@@ -55,3 +74,3 @@ if (symbol == null) {

}
this._symbolToName.set(symbol, name);
this._symbolToName.set(symbol, { name: name, kind: kind });
};

@@ -67,13 +86,30 @@ TypeContext.prototype.recordHasTypenameField = function (name) {

}
if (symbol.flags & ts.SymbolFlags.Alias) {
// Follow any aliases to get the real type declaration.
this._unresolvedTypes.set(name, this.resolveSymbol(symbol));
};
TypeContext.prototype.findSymbolDeclaration = function (startSymbol) {
var _a;
var symbol = this.resolveSymbol(startSymbol);
var declaration = (_a = symbol.declarations) === null || _a === void 0 ? void 0 : _a[0];
return declaration !== null && declaration !== void 0 ? declaration : null;
};
// Follow symbol aliases until we find the original symbol. Accounts for
// cyclical aliases.
TypeContext.prototype.resolveSymbol = function (startSymbol) {
var symbol = startSymbol;
var visitedSymbols = new Set();
while (ts.SymbolFlags.Alias & symbol.flags) {
if (visitedSymbols.has(symbol)) {
throw new Error("Cyclical alias detected. Breaking resolution.");
}
visitedSymbols.add(symbol);
symbol = this.checker.getAliasedSymbol(symbol);
}
this._unresolvedTypes.set(name, symbol);
return symbol;
};
TypeContext.prototype.resolveTypes = function (doc) {
var _a;
var _this = this;
var errors = [];
var newDoc = (0, graphql_1.visit)(doc, {
Name: function (t) {
var newDoc = (0, graphql_1.visit)(doc, (_a = {},
_a[graphql_1.Kind.NAME] = function (t) {
var namedTypeResult = _this.resolveNamedType(t);

@@ -85,4 +121,4 @@ if (namedTypeResult.kind === "ERROR") {

return namedTypeResult.value;
}
});
},
_a));
if (errors.length > 0) {

@@ -93,2 +129,139 @@ return (0, DiagnosticError_1.err)(errors);

};
TypeContext.prototype.handleAbstractDefinitions = function (docs) {
var e_1, _a;
var newDocs = [];
var errors = [];
var interfaceGraphResult = (0, InterfaceGraph_1.computeInterfaceMap)(this, docs);
if (interfaceGraphResult.kind === "ERROR") {
return interfaceGraphResult;
}
var interfaceGraph = interfaceGraphResult.value;
try {
for (var docs_1 = __values(docs), docs_1_1 = docs_1.next(); !docs_1_1.done; docs_1_1 = docs_1.next()) {
var doc = docs_1_1.value;
if (doc.kind === "AbstractFieldDefinition") {
var abstractDocResults = this.addAbstractFieldDefinition(doc, interfaceGraph);
if (abstractDocResults.kind === "ERROR") {
(0, helpers_1.extend)(errors, abstractDocResults.err);
}
else {
(0, helpers_1.extend)(newDocs, abstractDocResults.value);
}
}
else {
newDocs.push(doc);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (docs_1_1 && !docs_1_1.done && (_a = docs_1["return"])) _a.call(docs_1);
}
finally { if (e_1) throw e_1.error; }
}
if (errors.length > 0) {
return (0, DiagnosticError_1.err)(errors);
}
return (0, DiagnosticError_1.ok)(newDocs);
};
// A field definition may be on a concrete type, or on an interface. If it's on an interface,
// we need to add it to each concrete type that implements the interface.
TypeContext.prototype.addAbstractFieldDefinition = function (doc, interfaceGraph) {
var e_2, _a;
var _b;
var newDocs = [];
var typeNameResult = this.resolveNamedType(doc.onType);
if (typeNameResult.kind === "ERROR") {
return (0, DiagnosticError_1.err)([typeNameResult.err]);
}
var symbol = this._unresolvedTypes.get(doc.onType);
if (symbol == null) {
// This should have already been handled by resolveNamedType
throw new Error("Expected to find unresolved type.");
}
var nameDefinition = this._symbolToName.get(symbol);
if (nameDefinition == null) {
// This should have already been handled by resolveNamedType
throw new Error("Expected to find name definition.");
}
switch (nameDefinition.kind) {
case "TYPE":
// Extending a type, is just adding a field to it.
newDocs.push({
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION,
name: doc.onType,
fields: [doc.field],
loc: doc.loc
});
break;
case "INTERFACE": {
// Extending an interface is a bit more complicated. We need to add the field
// to the interface, and to each type that implements the interface.
// The interface field definition is not executable, so we don't
// need to annotate it with the details of the implementation.
var directives = (_b = doc.field.directives) === null || _b === void 0 ? void 0 : _b.filter(function (directive) {
return directive.name.value !== serverDirectives_1.EXPORTED_DIRECTIVE;
});
newDocs.push({
kind: graphql_1.Kind.INTERFACE_TYPE_EXTENSION,
name: doc.onType,
fields: [__assign(__assign({}, doc.field), { directives: directives })]
});
try {
for (var _c = __values(interfaceGraph.get(nameDefinition.name.value)), _d = _c.next(); !_d.done; _d = _c.next()) {
var implementor = _d.value;
var name = {
kind: graphql_1.Kind.NAME,
value: implementor.name,
loc: doc.loc
};
switch (implementor.kind) {
case "TYPE":
newDocs.push({
kind: graphql_1.Kind.OBJECT_TYPE_EXTENSION,
name: name,
fields: [doc.field],
loc: doc.loc
});
break;
case "INTERFACE":
newDocs.push({
kind: graphql_1.Kind.INTERFACE_TYPE_EXTENSION,
name: name,
fields: [__assign(__assign({}, doc.field), { directives: directives })],
loc: doc.loc
});
break;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_d && !_d.done && (_a = _c["return"])) _a.call(_c);
}
finally { if (e_2) throw e_2.error; }
}
break;
}
default: {
// Extending any other type of definition is not supported.
var loc = doc.onType.loc;
if (loc == null) {
throw new Error("Expected onType to have a location.");
}
var relatedLoc = nameDefinition.name.loc;
if (relatedLoc == null) {
throw new Error("Expected nameDefinition to have a location.");
}
return (0, DiagnosticError_1.err)([
this.err(loc, E.invalidTypePassedToFieldFunction(), [
this.relatedInformation(relatedLoc, "This is the type that was passed to `@".concat(Extractor_1.FIELD_TAG, "`.")),
]),
]);
}
}
return (0, DiagnosticError_1.ok)(newDocs);
};
TypeContext.prototype.resolveNamedType = function (unresolved) {

@@ -103,18 +276,32 @@ var symbol = this._unresolvedTypes.get(unresolved);

}
var name = this._symbolToName.get(symbol);
if (name == null) {
var nameDefinition = this._symbolToName.get(symbol);
if (nameDefinition == null) {
if (unresolved.loc == null) {
throw new Error("Expected namedType to have a location.");
}
return (0, DiagnosticError_1.err)({
messageText: "This type is not a valid GraphQL type. Did you mean to annotate it's definition with a `/** @gql */` tag such as `/** @gqlType */` or `/** @gqlInput **/`?",
start: unresolved.loc.start,
length: unresolved.loc.end - unresolved.loc.start,
category: ts.DiagnosticCategory.Error,
code: DiagnosticError_1.FAKE_ERROR_CODE,
file: ts.createSourceFile(unresolved.loc.source.name, unresolved.loc.source.body, ts.ScriptTarget.Latest)
});
return (0, DiagnosticError_1.err)(this.err(unresolved.loc, E.unresolvedTypeReference()));
}
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: name }));
return (0, DiagnosticError_1.ok)(__assign(__assign({}, unresolved), { value: nameDefinition.name.value }));
};
TypeContext.prototype.err = function (loc, message, relatedInformation) {
return {
messageText: message,
start: loc.start,
length: loc.end - loc.start,
category: ts.DiagnosticCategory.Error,
code: DiagnosticError_1.FAKE_ERROR_CODE,
file: ts.createSourceFile(loc.source.name, loc.source.body, ts.ScriptTarget.Latest),
relatedInformation: relatedInformation
};
};
TypeContext.prototype.relatedInformation = function (loc, message) {
return {
category: ts.DiagnosticCategory.Message,
code: DiagnosticError_1.FAKE_ERROR_CODE,
messageText: message,
file: ts.createSourceFile(loc.source.name, loc.source.body, ts.ScriptTarget.Latest),
start: loc.start,
length: loc.end - loc.start
};
};
TypeContext.prototype.validateInterfaceImplementorsHaveTypenameField = function () {

@@ -121,0 +308,0 @@ return (0, DiagnosticError_1.ok)(null);

"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
exports.__esModule = true;

@@ -42,2 +69,3 @@ exports.graphqlSourceToSourceFile = exports.diagnosticAtGraphQLLocation = exports.graphQlErrorToDiagnostic = exports.FAKE_ERROR_CODE = exports.ReportableDiagnostics = exports.err = exports.ok = void 0;

function graphQlErrorToDiagnostic(error) {
var e_1, _a;
var position = error.positions[0];

@@ -47,2 +75,41 @@ if (position == null) {

}
// Start with baseline location infromation
var start = position;
var length = 1;
var relatedInformation;
// Nodes have actual ranges (not just a single position), so we we have one
// (or more!) use that instead.
if (error.nodes != null && error.nodes.length > 0) {
var _b = __read(error.nodes), node = _b[0], rest = _b.slice(1);
if (node.loc != null) {
start = node.loc.start;
length = node.loc.end - node.loc.start;
if (rest.length > 0) {
relatedInformation = [];
try {
for (var rest_1 = __values(rest), rest_1_1 = rest_1.next(); !rest_1_1.done; rest_1_1 = rest_1.next()) {
var relatedNode = rest_1_1.value;
if (relatedNode.loc == null) {
continue;
}
relatedInformation.push({
category: ts.DiagnosticCategory.Message,
code: exports.FAKE_ERROR_CODE,
messageText: "Related location",
file: graphqlSourceToSourceFile(relatedNode.loc.source),
start: relatedNode.loc.start,
length: relatedNode.loc.end - relatedNode.loc.start
});
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (rest_1_1 && !rest_1_1.done && (_a = rest_1["return"])) _a.call(rest_1);
}
finally { if (e_1) throw e_1.error; }
}
}
}
}
var sourceFile;

@@ -57,5 +124,5 @@ if (error.source != null) {

category: ts.DiagnosticCategory.Error,
start: position,
// FIXME: Improve ranges
length: 1
start: start,
length: length,
relatedInformation: relatedInformation
};

@@ -62,0 +129,0 @@ }

{
"name": "grats",
"version": "0.0.0-main-57a88f40",
"version": "0.0.0-main-5ac400f9",
"main": "dist/src/index.js",

@@ -32,4 +32,8 @@ "bin": "dist/src/cli.js",

"packageManager": "pnpm@8.1.1",
"engines": {
"node": ">=16 <=21",
"pnpm": "^8"
},
"scripts": {
"test": "ts-node --esm src/tests/test.ts",
"test": "ts-node src/tests/test.ts",
"integration-tests": "node src/tests/integration.mjs",

@@ -36,0 +40,0 @@ "build": "tsc --build",

@@ -11,3 +11,3 @@ # -=[ ALPHA SOFTWARE ]=-

When you write your GraphQL server in TypeScript, your fields and resovlers
When you write your GraphQL server in TypeScript, your fields and resolvers
are _already_ annotated with type information. _Grats leverages your existing

@@ -18,3 +18,3 @@ type annotations to automatically extract an executable GraphQL schema from your

By making your TypeScript implementation the source of truth, you never have to
worry about valiating that your implementiaton matches your schema. Your
worry about valuating that your implementation matches your schema. Your
implementation _is_ your schema!

@@ -30,9 +30,9 @@

* [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea.
* [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project.
* [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs.
* Everyone who worked on Meta's Hack GraphQL server, the developer experince of which inspired this project.
* A number of other projects which seem to have explored similar ideas in the past:
* [ts2gql](https://github.com/convoyinc/ts2gql)
* [ts2graphql](https://github.com/cevek/ts2graphql)
* [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)
- [@mofeiZ](https://github.com/mofeiZ) and [@alunyov](https://github/alunyov) for their Relay hack-week project exploring a similar idea.
- [@josephsavona](https://github.com/josephsavona) for input on the design of [Relay Resolvers](https://relay.dev/docs/guides/relay-resolvers/) which inspired this project.
- [@bradzacher](https://github.com/bradzacher) for tips on how to handle TypeScript ASTs.
- Everyone who worked on Meta's Hack GraphQL server, the developer experince of which inspired this project.
- A number of other projects which seem to have explored similar ideas in the past:
- [ts2gql](https://github.com/convoyinc/ts2gql)
- [ts2graphql](https://github.com/cevek/ts2graphql)
- [typegraphql-reflection-poc](https://github.com/MichalLytek/typegraphql-reflection-poc)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc