Comparing version 0.0.0-main-f83007b7 to 0.0.0-main-f9706d91
@@ -0,1 +1,11 @@ | ||
/** | ||
* Error messages for Grats | ||
* | ||
* Ideally each error message conveys all of the following: | ||
* - What went wrong | ||
* - What Grats expected with an example | ||
* - Why Grats expected that | ||
* - A suggestion for how to fix the error | ||
* - A link to the Grats documentation | ||
*/ | ||
export declare function fieldTagOnWrongNode(): string; | ||
@@ -58,1 +68,3 @@ export declare function killsParentOnExceptionOnWrongNode(): string; | ||
export declare function killsParentOnExceptionOnNullable(): string; | ||
export declare function nonNullTypeCannotBeOptional(): string; | ||
export declare function mergedInterfaces(interfaceName: string): string; |
"use strict"; | ||
exports.__esModule = true; | ||
exports.defaultArgPropertyMissingName = exports.defaultArgElementIsNotAssignment = exports.defaultValueIsNotLiteral = exports.ambiguousNumberType = exports.expectedOneNonNullishType = exports.propertyFieldMissingType = exports.cannotResolveSymbolForDescription = exports.promiseMissingTypeArg = exports.methodMissingType = exports.gqlEntityMissingName = exports.enumVariantMissingInitializer = exports.enumVariantNotStringLiteral = exports.enumTagOnInvalidNode = exports.argNotTyped = exports.argNameNotLiteral = exports.argIsNotProperty = exports.argumentParamIsNotObject = exports.argumentParamIsMissingType = exports.typeNameDoesNotMatchExpected = exports.typeNameTypeNotStringLiteral = exports.typeNameMissingTypeAnnotation = exports.typeNameInitializerWrong = exports.typeNameInitializeNotString = exports.typeNameMissingInitializer = exports.typeNameNotDeclaration = exports.typeTagOnAliasOfNonObject = exports.typeTagOnUnamedClass = exports.inputFieldUntyped = exports.inputTypeFieldNotProperty = exports.inputTypeNotLiteral = exports.functionFieldNotNamedExport = exports.functionFieldDefaultExport = exports.functionFieldNotNamed = exports.functionFieldParentTypeNotValid = exports.functionFieldParentTypeMissing = exports.functionFieldNotTopLevel = exports.invalidReturnTypeForFunctionField = exports.invalidParentArgForFunctionField = exports.expectedUnionTypeReference = exports.expectedUnionTypeNode = exports.invalidUnionTagUsage = exports.invalidInputTagUsage = exports.invalidEnumTagUsage = exports.invalidInterfaceTagUsage = exports.invalidScalarTagUsage = exports.invalidTypeTagUsage = exports.invalidGratsTag = exports.wrongCasingForGratsTag = exports.killsParentOnExceptionOnWrongNode = exports.fieldTagOnWrongNode = void 0; | ||
exports.killsParentOnExceptionOnNullable = exports.killsParentOnExceptionWithWrongConfig = exports.expectedIdentifer = exports.pluralTypeMissingParameter = exports.unknownGraphQLType = exports.unsupportedTypeLiteral = exports.defaultArgPropertyMissingInitializer = void 0; | ||
exports.mergedInterfaces = exports.nonNullTypeCannotBeOptional = exports.killsParentOnExceptionOnNullable = exports.killsParentOnExceptionWithWrongConfig = exports.expectedIdentifer = exports.pluralTypeMissingParameter = exports.unknownGraphQLType = exports.unsupportedTypeLiteral = exports.defaultArgPropertyMissingInitializer = void 0; | ||
var Extractor_1 = require("./Extractor"); | ||
// TODO: Move these to short URLS that are easier to keep from breaking. | ||
var DOC_URLS = { | ||
mergedInterfaces: "https://grats.capt.dev/docs/dockblock-tags/interfaces/#merged-interfaces" | ||
}; | ||
/** | ||
* Error messages for Grats | ||
* | ||
* Ideally each error message conveys all of the following: | ||
* - What went wrong | ||
* - What Grats expected with an example | ||
* - Why Grats expected that | ||
* - A suggestion for how to fix the error | ||
* - A link to the Grats documentation | ||
*/ | ||
function fieldTagOnWrongNode() { | ||
@@ -235,1 +249,15 @@ return "`@".concat(Extractor_1.FIELD_TAG, "` can only be used on method/property declarations or signatures."); | ||
exports.killsParentOnExceptionOnNullable = killsParentOnExceptionOnNullable; | ||
function nonNullTypeCannotBeOptional() { | ||
return "Unexpected optional argument that does not also accept `null`. Optional arguments in GraphQL may get passed an explict `null` value. This means optional arguments must be typed to also accept `null`."; | ||
} | ||
exports.nonNullTypeCannotBeOptional = nonNullTypeCannotBeOptional; | ||
function mergedInterfaces(interfaceName) { | ||
return [ | ||
"Unexpected merged interface `".concat(interfaceName, "`."), | ||
"If an interface is declared multiple times in a scope, TypeScript merges them.", | ||
"To avoid ambiguity Grats does not support using merged interfaces as GraphQL interfaces.", | ||
"Consider using a unique name for your TypeScript interface and renaming it.\n\n", | ||
"Learn more: ".concat(DOC_URLS.mergedInterfaces), | ||
].join(" "); | ||
} | ||
exports.mergedInterfaces = mergedInterfaces; |
@@ -44,4 +44,5 @@ import { DefinitionNode, FieldDefinitionNode, InputValueDefinitionNode, ListTypeNode, NamedTypeNode, Location as GraphQLLocation, NameNode, Token, TypeNode, NonNullTypeNode, StringValueNode, ConstValueNode, ConstDirectiveNode, ConstArgumentNode, EnumValueDefinitionNode, ConstObjectFieldNode, ConstObjectValueNode, ConstListValueNode } from "graphql"; | ||
/** 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, message: string, relatedInformation?: ts.DiagnosticRelatedInformation[]): null; | ||
related(node: ts.Node, message: string): ts.DiagnosticRelatedInformation; | ||
diagnosticAnnotatedLocation(node: ts.Node): { | ||
@@ -72,3 +73,3 @@ start: number; | ||
collectInterfaces(node: ts.ClassDeclaration | ts.InterfaceDeclaration): Array<NamedTypeNode> | null; | ||
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): void; | ||
interfaceInterfaceDeclaration(node: ts.InterfaceDeclaration, tag: ts.JSDocTag): null | undefined; | ||
collectFields(node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeLiteralNode): Array<FieldDefinitionNode>; | ||
@@ -75,0 +76,0 @@ collectArgs(argsParam: ts.ParameterDeclaration): ReadonlyArray<InputValueDefinitionNode> | null; |
@@ -13,2 +13,18 @@ "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; | ||
@@ -200,3 +216,3 @@ exports.Extractor = exports.ALL_TAGS = exports.KILLS_PARENT_ON_EXCEPTION_TAG = 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; | ||
/** Error handling and location juggling */ | ||
Extractor.prototype.report = function (node, message) { | ||
Extractor.prototype.report = function (node, message, relatedInformation) { | ||
var start = node.getStart(); | ||
@@ -210,3 +226,4 @@ var length = node.getEnd() - start; | ||
start: start, | ||
length: length | ||
length: length, | ||
relatedInformation: relatedInformation | ||
}); | ||
@@ -217,8 +234,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) { | ||
Extractor.prototype.reportUnhandled = function (node, message, relatedInformation) { | ||
var suggestion = "If you think ".concat(exports.LIBRARY_NAME, " should be able to infer this type, 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) { | ||
@@ -603,2 +629,3 @@ var start = node.getStart(); | ||
Extractor.prototype.interfaceInterfaceDeclaration = function (node, tag) { | ||
var _this = this; | ||
var name = this.entityName(node, tag); | ||
@@ -608,2 +635,17 @@ 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; }) | ||
.map(function (d) { | ||
var _a; | ||
var locNode = (_a = ts.getNameOfDeclaration(d)) !== null && _a !== void 0 ? _a : d; | ||
return _this.related(locNode, "Other declaration"); | ||
}); | ||
return this.report(node.name, E.mergedInterfaces(name.value), otherLocations); | ||
} | ||
var description = this.collectDescription(node.name); | ||
@@ -724,4 +766,3 @@ var fields = this.collectFields(node); | ||
} | ||
this.reportUnhandled(node, E.defaultValueIsNotLiteral()); | ||
return null; | ||
return this.reportUnhandled(node, E.defaultValueIsNotLiteral()); | ||
}; | ||
@@ -832,2 +873,8 @@ Extractor.prototype.collectArrayLiteral = function (node) { | ||
if (node.questionToken) { | ||
/* | ||
// 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.gqlNullableType(type); | ||
@@ -1154,2 +1201,4 @@ } | ||
}; | ||
// 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) { | ||
@@ -1171,5 +1220,4 @@ var _this = this; | ||
var types = node.types.filter(function (type) { return !_this.isNullish(type); }); | ||
if (types.length !== 1) { | ||
this.report(node, E.expectedOneNonNullishType()); | ||
return null; | ||
if (types.length === 0) { | ||
return this.report(node, E.expectedOneNonNullishType()); | ||
} | ||
@@ -1179,2 +1227,11 @@ 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) { | ||
@@ -1181,0 +1238,0 @@ return this.gqlNullableType(type); |
@@ -65,26 +65,33 @@ "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 graphql_1 = require("graphql"); | ||
var commander_1 = require("commander"); | ||
var program = new commander_1.Command(); | ||
program | ||
.name("grats-tests") | ||
.description("Run Grats' internal tests") | ||
.option("-w, --write", "Write the actual ouput 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 +102,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 +108,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 +121,5 @@ finally { if (e_1) throw e_1.error; } | ||
}); | ||
} | ||
}); | ||
var fixturesDir = path.join(__dirname, "fixtures"); | ||
var integrationFixturesDir = path.join(__dirname, "integrationFixtures"); | ||
var testDirs = [ | ||
@@ -148,3 +156,50 @@ { | ||
}, | ||
{ | ||
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 @@ } |
{ | ||
"name": "grats", | ||
"version": "0.0.0-main-f83007b7", | ||
"version": "0.0.0-main-f9706d91", | ||
"main": "dist/src/index.js", | ||
@@ -5,0 +5,0 @@ "bin": "dist/src/cli.js", |
159398
35
3352
6