ts-auto-guard
Advanced tools
Comparing version 1.0.0-alpha.4 to 1.0.0-alpha.5
@@ -5,3 +5,3 @@ import { Project } from 'ts-morph'; | ||
shortCircuitCondition?: string; | ||
debug: boolean; | ||
debug?: boolean; | ||
} | ||
@@ -8,0 +8,0 @@ export interface IGenerateOptions { |
196
lib/index.js
@@ -88,3 +88,3 @@ "use strict"; | ||
return (lodash_1.flatMap(symbol.getDeclarations(), function (d) { return __spread([d], d.getAncestors()); }) | ||
.filter(ts_morph_1.TypeGuards.isExportableNode) | ||
.filter(ts_morph_1.Node.isExportableNode) | ||
.find(function (n) { return n.isExported(); }) || null); | ||
@@ -121,6 +121,6 @@ } | ||
var declaration = _c.value; | ||
if (ts_morph_1.TypeGuards.isClassDeclaration(declaration)) { | ||
if (ts_morph_1.Node.isClassDeclaration(declaration)) { | ||
return true; | ||
} | ||
if (ts_morph_1.TypeGuards.isVariableDeclaration(declaration) && | ||
if (ts_morph_1.Node.isVariableDeclaration(declaration) && | ||
declaration.getType().getConstructSignatures().length > 0) { | ||
@@ -155,3 +155,2 @@ return true; | ||
var e_2, _a, e_3, _b; | ||
var _c; | ||
var jsDocs = child.getJsDocs(); | ||
@@ -162,4 +161,4 @@ try { | ||
try { | ||
for (var _d = (e_3 = void 0, __values(doc.getInnerText().split('\n'))), _e = _d.next(); !_e.done; _e = _d.next()) { | ||
var line = _e.value; | ||
for (var _c = (e_3 = void 0, __values(doc.getInnerText().split('\n'))), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var line = _d.value; | ||
var match = line | ||
@@ -169,3 +168,3 @@ .trim() | ||
if (match !== null) { | ||
var _f = __read(match, 3), typeGuardName = _f[1], command = _f[2]; | ||
var _e = __read(match, 3), typeGuardName = _e[1], command = _e[2]; | ||
if (command !== 'type-guard') { | ||
@@ -182,3 +181,3 @@ reportError("command " + command + " is not supported!"); | ||
try { | ||
if (_e && !_e.done && (_b = _d.return)) _b.call(_d); | ||
if (_d && !_d.done && (_b = _c.return)) _b.call(_c); | ||
} | ||
@@ -198,3 +197,4 @@ finally { if (e_3) throw e_3.error; } | ||
var t = child.getType(); | ||
var name = (_c = t.getSymbol()) === null || _c === void 0 ? void 0 : _c.getName(); | ||
var symbol = t.getSymbol() || t.getAliasSymbol(); | ||
var name = symbol === null || symbol === void 0 ? void 0 : symbol.getName(); | ||
if (name) { | ||
@@ -227,7 +227,7 @@ return 'is' + name; | ||
} | ||
function typeUnionConditions(varName, types, addDependency, project, path, arrayDepth, options) { | ||
function typeUnionConditions(varName, types, addDependency, project, path, arrayDepth, records, options) { | ||
var conditions = []; | ||
conditions.push.apply(conditions, __spread(types | ||
.map(function (type) { | ||
return typeConditions(varName, type, addDependency, project, path, arrayDepth, true, options); | ||
return typeConditions(varName, type, addDependency, project, path, arrayDepth, true, records, options); | ||
}) | ||
@@ -240,3 +240,3 @@ .filter(function (v) { return v !== null; }))); | ||
} | ||
function arrayCondition(varName, arrayType, addDependency, project, path, arrayDepth, options) { | ||
function arrayCondition(varName, arrayType, addDependency, project, path, arrayDepth, records, options) { | ||
if (arrayType.getText() === 'never') { | ||
@@ -247,3 +247,3 @@ return ands("Array.isArray(" + varName + ")", eq(varName + ".length", '0')); | ||
var elementPath = path + "[${" + indexIdentifier + "}]"; | ||
var conditions = typeConditions('e', arrayType, addDependency, project, elementPath, arrayDepth + 1, true, options); | ||
var conditions = typeConditions('e', arrayType, addDependency, project, elementPath, arrayDepth + 1, true, records, options); | ||
if (conditions === null) { | ||
@@ -264,3 +264,4 @@ reportError("No conditions for " + varName + ", with array type " + arrayType.getText()); | ||
} | ||
function objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, options) { | ||
function objectCondition(varName, type, addDependency, project, path, arrayDepth, records, options) { | ||
var _a; | ||
var conditions = []; | ||
@@ -281,36 +282,18 @@ var symbol = type.getSymbol(); | ||
} | ||
// JSDoc is attached to the type alias rather than the object literal in the | ||
// case of eg. `type Foo = { x: number }` | ||
var declarationResolved = ts_morph_1.Node.isTypeAliasDeclaration(declaration) | ||
? declaration.getParentIfKind(ts_morph_1.SyntaxKind.TypeAliasDeclaration) | ||
: declaration; | ||
var jsDocable = ts_morph_1.Node.isJSDocableNode(declarationResolved) | ||
? declarationResolved | ||
: declarationResolved === null || declarationResolved === void 0 ? void 0 : declarationResolved.getParent(); | ||
var typeGuardName = ts_morph_1.Node.isJSDocableNode(jsDocable) | ||
? getTypeGuardName(jsDocable, options) | ||
: null; | ||
if (useGuard && typeGuardName !== null) { | ||
var sourcePath = declaration.getSourceFile().getFilePath(); | ||
addDependency(findOrCreate(project, outFilePath(sourcePath)), typeGuardName, false); | ||
// NOTE: Cast to boolean to stop type guard property and prevent compile | ||
// errors. | ||
return typeGuardName + "(" + varName + ") as boolean"; | ||
} | ||
if (type.isInterface()) { | ||
if (!useGuard || typeGuardName === null) { | ||
if (!ts_morph_1.Node.isInterfaceDeclaration(declaration)) { | ||
throw new TypeError('Extected declaration to be an interface delcaration!'); | ||
if (!ts_morph_1.Node.isInterfaceDeclaration(declaration)) { | ||
throw new TypeError('Extected declaration to be an interface delcaration!'); | ||
} | ||
declaration.getBaseTypes().forEach(function (baseType) { | ||
var condition = typeConditions(varName, baseType, addDependency, project, path, arrayDepth, true, records, options); | ||
if (condition !== null) { | ||
conditions.push(condition); | ||
} | ||
declaration.getBaseTypes().forEach(function (baseType) { | ||
var condition = typeConditions(varName, baseType, addDependency, project, path, arrayDepth, true, options); | ||
if (condition !== null) { | ||
conditions.push(condition); | ||
} | ||
}); | ||
if (conditions.length === 0) { | ||
conditions.push(objectTypeCondition(varName, type)); | ||
} | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, declaration.getProperties(), addDependency, project, path, arrayDepth, options))); | ||
}); | ||
if (conditions.length === 0) { | ||
conditions.push(objectTypeCondition(varName, type)); | ||
} | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, declaration | ||
.getProperties() | ||
.map(function (p) { return ({ name: p.getName(), type: p.getType() }); }), addDependency, project, path, arrayDepth, records, options))); | ||
} | ||
@@ -322,4 +305,14 @@ else { | ||
var properties = type.getProperties(); | ||
var propertySignatures = properties.map(function (p) { return p.getDeclarations()[0]; }); | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, propertySignatures, addDependency, project, path, arrayDepth, options))); | ||
var typeDeclarations_1 = (_a = type.getSymbol()) === null || _a === void 0 ? void 0 : _a.getDeclarations(); | ||
var propertySignatures = properties.map(function (p) { | ||
var propertyDeclarations = p.getDeclarations(); | ||
var typeAtLocation = propertyDeclarations.length !== 0 | ||
? p.getTypeAtLocation(propertyDeclarations[0]) | ||
: p.getTypeAtLocation((typeDeclarations_1 || [])[0]); | ||
return { | ||
name: p.getName(), | ||
type: typeAtLocation, | ||
}; | ||
}); | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, propertySignatures, addDependency, project, path, arrayDepth, records, options))); | ||
} | ||
@@ -335,6 +328,6 @@ catch (error) { | ||
} | ||
function tupleCondition(varName, type, addDependency, project, path, arrayDepth, options) { | ||
function tupleCondition(varName, type, addDependency, project, path, arrayDepth, records, options) { | ||
var types = type.getTupleElements(); | ||
var conditions = types.reduce(function (acc, elementType, i) { | ||
var condition = typeConditions(varName + "[" + i + "]", elementType, addDependency, project, path, arrayDepth, true, options); | ||
var condition = typeConditions(varName + "[" + i + "]", elementType, addDependency, project, path, arrayDepth, true, records, options); | ||
if (condition !== null) { | ||
@@ -352,3 +345,3 @@ acc.push(condition); | ||
.getDeclarations() | ||
.find(ts_morph_1.TypeGuards.isEnumMember) | ||
.find(ts_morph_1.Node.isEnumMember) | ||
.getParent(); | ||
@@ -359,3 +352,3 @@ if (node === undefined) { | ||
} | ||
if (!ts_morph_1.TypeGuards.isEnumDeclaration(node)) { | ||
if (!ts_morph_1.Node.isEnumDeclaration(node)) { | ||
reportError('Enum literal parent was not an enum declaration'); | ||
@@ -365,6 +358,19 @@ return null; | ||
typeToDependency(type, addDependency); | ||
// type.getText() returns incorrect module name for some reason | ||
return eq(varName, node.getSymbol().getName() + "." + type.getSymbol().getName()); | ||
} | ||
return eq(varName, type.getText()); | ||
} | ||
function typeConditions(varName, type, addDependency, project, path, arrayDepth, useGuard, options) { | ||
function reusedCondition(type, records, varName) { | ||
var record = records.find(function (x) { return x.typeDeclaration.getType() === type; }); | ||
if (record) { | ||
return record.guardName + "(" + varName + ") as boolean"; | ||
} | ||
return null; | ||
} | ||
function typeConditions(varName, type, addDependency, project, path, arrayDepth, useGuard, records, options) { | ||
var reused = reusedCondition(type, records, varName); | ||
if (useGuard && reused) { | ||
return reused; | ||
} | ||
if (type.isNull()) { | ||
@@ -388,12 +394,12 @@ return eq(varName, 'null'); | ||
} | ||
return typeUnionConditions(varName, type.getUnionTypes(), addDependency, project, path, arrayDepth, options); | ||
return typeUnionConditions(varName, type.getUnionTypes(), addDependency, project, path, arrayDepth, records, options); | ||
} | ||
if (type.isIntersection()) { | ||
return typeUnionConditions(varName, type.getIntersectionTypes(), addDependency, project, path, arrayDepth, options); | ||
return typeUnionConditions(varName, type.getIntersectionTypes(), addDependency, project, path, arrayDepth, records, options); | ||
} | ||
if (type.isArray()) { | ||
return arrayCondition(varName, type.getArrayElementType(), addDependency, project, path, arrayDepth, options); | ||
return arrayCondition(varName, type.getArrayElementType(), addDependency, project, path, arrayDepth, records, options); | ||
} | ||
if (isReadonlyArrayType(type)) { | ||
return arrayCondition(varName, getReadonlyArrayType(type), addDependency, project, path, arrayDepth, options); | ||
return arrayCondition(varName, getReadonlyArrayType(type), addDependency, project, path, arrayDepth, records, options); | ||
} | ||
@@ -405,6 +411,6 @@ if (isClassType(type)) { | ||
if (type.isObject()) { | ||
return objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, options); | ||
return objectCondition(varName, type, addDependency, project, path, arrayDepth, records, options); | ||
} | ||
if (type.isTuple()) { | ||
return tupleCondition(varName, type, addDependency, project, path, arrayDepth, options); | ||
return tupleCondition(varName, type, addDependency, project, path, arrayDepth, records, options); | ||
} | ||
@@ -416,10 +422,9 @@ if (type.isLiteral()) { | ||
} | ||
function propertyConditions(objName, property, addDependency, project, path, arrayDepth, options) { | ||
function propertyConditions(objName, property, addDependency, project, path, arrayDepth, records, options) { | ||
var debug = options.debug; | ||
// working around a bug in ts-simple-ast | ||
var propertyName = property === undefined ? '(???)' : property.getName(); | ||
var propertyName = property.name; | ||
var varName = objName + "." + propertyName; | ||
var propertyPath = path + "." + propertyName; | ||
var expectedType = property.getType().getText(); | ||
var conditions = typeConditions(varName, property.getType(), addDependency, project, propertyPath, arrayDepth, true, options); | ||
var expectedType = property.type.getText(); | ||
var conditions = typeConditions(varName, property.type, addDependency, project, propertyPath, arrayDepth, true, records, options); | ||
if (debug) { | ||
@@ -431,14 +436,15 @@ return (conditions && | ||
} | ||
function propertiesConditions(varName, properties, addDependency, project, path, arrayDepth, options) { | ||
function propertiesConditions(varName, properties, addDependency, project, path, arrayDepth, records, options) { | ||
return properties | ||
.map(function (prop) { | ||
return propertyConditions(varName, prop, addDependency, project, path, arrayDepth, options); | ||
return propertyConditions(varName, prop, addDependency, project, path, arrayDepth, records, options); | ||
}) | ||
.filter(function (v) { return v !== null; }); | ||
} | ||
function generateTypeGuard(functionName, typeName, type, addDependency, project, options) { | ||
function generateTypeGuard(functionName, typeDeclaration, addDependency, project, records, options) { | ||
var debug = options.debug, shortCircuitCondition = options.shortCircuitCondition; | ||
var typeName = typeDeclaration.getName(); | ||
var defaultArgumentName = lodash_1.lowerFirst(typeName); | ||
var conditions = typeConditions('obj', type, addDependency, project, '${argumentName}', // tslint:disable-line:no-invalid-template-strings | ||
0, false, options); | ||
var conditions = typeConditions('obj', typeDeclaration.getType(), addDependency, project, '${argumentName}', // tslint:disable-line:no-invalid-template-strings | ||
0, false, records, options); | ||
var secondArgument = debug | ||
@@ -453,10 +459,2 @@ ? "argumentName: string = \"" + defaultArgumentName + "\"" | ||
} | ||
// -- Process project -- | ||
function findOrCreate(project, path) { | ||
var outFile = project.getSourceFile(path); | ||
if (outFile === undefined) { | ||
outFile = project.createSourceFile(path); | ||
} | ||
return outFile; | ||
} | ||
function createAddDependency(dependencies) { | ||
@@ -505,3 +503,3 @@ return function addDependency(sourceFile, name, isDefault) { | ||
project.getSourceFiles().forEach(function (sourceFile) { | ||
var e_4, _a, e_5, _b; | ||
var e_4, _a, e_5, _b, e_6, _c, e_7, _d; | ||
var dependencies = new Map(); | ||
@@ -511,2 +509,3 @@ var addDependency = createAddDependency(dependencies); | ||
var exports = Array.from(sourceFile.getExportedDeclarations().values()); | ||
var allTypesDeclarations = []; | ||
try { | ||
@@ -519,8 +518,5 @@ for (var exports_1 = __values(exports), exports_1_1 = exports_1.next(); !exports_1_1.done; exports_1_1 = exports_1.next()) { | ||
if (ts_morph_1.Node.isTypeAliasDeclaration(singleExport) || | ||
ts_morph_1.Node.isInterfaceDeclaration(singleExport)) { | ||
var typeGuardName = getTypeGuardName(singleExport, options); | ||
if (typeGuardName !== null) { | ||
functions.push(generateTypeGuard(typeGuardName, singleExport.getName(), singleExport.getType(), addDependency, project, options)); | ||
addDependency(sourceFile, singleExport.getName(), singleExport.isDefaultExport()); | ||
} | ||
ts_morph_1.Node.isInterfaceDeclaration(singleExport) || | ||
ts_morph_1.Node.isEnumDeclaration(singleExport)) { | ||
allTypesDeclarations.push(singleExport); | ||
} | ||
@@ -545,2 +541,36 @@ } | ||
} | ||
var records = []; | ||
try { | ||
for (var allTypesDeclarations_1 = __values(allTypesDeclarations), allTypesDeclarations_1_1 = allTypesDeclarations_1.next(); !allTypesDeclarations_1_1.done; allTypesDeclarations_1_1 = allTypesDeclarations_1.next()) { | ||
var typeDeclaration = allTypesDeclarations_1_1.value; | ||
var typeGuardName = getTypeGuardName(typeDeclaration, options); | ||
if (typeGuardName !== null) { | ||
records.push({ guardName: typeGuardName, typeDeclaration: typeDeclaration }); | ||
} | ||
} | ||
} | ||
catch (e_6_1) { e_6 = { error: e_6_1 }; } | ||
finally { | ||
try { | ||
if (allTypesDeclarations_1_1 && !allTypesDeclarations_1_1.done && (_c = allTypesDeclarations_1.return)) _c.call(allTypesDeclarations_1); | ||
} | ||
finally { if (e_6) throw e_6.error; } | ||
} | ||
try { | ||
for (var allTypesDeclarations_2 = __values(allTypesDeclarations), allTypesDeclarations_2_1 = allTypesDeclarations_2.next(); !allTypesDeclarations_2_1.done; allTypesDeclarations_2_1 = allTypesDeclarations_2.next()) { | ||
var typeDeclaration = allTypesDeclarations_2_1.value; | ||
var typeGuardName = getTypeGuardName(typeDeclaration, options); | ||
if (typeGuardName !== null) { | ||
functions.push(generateTypeGuard(typeGuardName, typeDeclaration, addDependency, project, records, options)); | ||
addDependency(sourceFile, typeDeclaration.getName(), typeDeclaration.isDefaultExport()); | ||
} | ||
} | ||
} | ||
catch (e_7_1) { e_7 = { error: e_7_1 }; } | ||
finally { | ||
try { | ||
if (allTypesDeclarations_2_1 && !allTypesDeclarations_2_1.done && (_d = allTypesDeclarations_2.return)) _d.call(allTypesDeclarations_2); | ||
} | ||
finally { if (e_7) throw e_7.error; } | ||
} | ||
if (functions.length > 0) { | ||
@@ -547,0 +577,0 @@ if (options.debug) { |
{ | ||
"name": "ts-auto-guard", | ||
"version": "1.0.0-alpha.4", | ||
"version": "1.0.0-alpha.5", | ||
"description": "Generate type guard functions from TypeScript interfaces", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/rhys-vdw/ts-auto-guard", |
@@ -71,3 +71,4 @@ import { each, pull } from 'lodash' | ||
t.equal(sourceText, expectedFile.getText(), filePath) | ||
const expectedText = expectedFile.getText() | ||
t.equal(sourceText, expectedText, filePath) | ||
} | ||
@@ -483,1 +484,164 @@ } | ||
) | ||
testProcessProject( | ||
'generates type guards for mapped types', | ||
{ | ||
'test.ts': ` | ||
/** @see {isPropertyValueType} ts-auto-guard:type-guard */ | ||
export type PropertyValueType = {value: string}; | ||
/** @see {isPropertyName} ts-auto-guard:type-guard */ | ||
export type PropertyName = 'name' | 'value'; | ||
/** @see {isFoo} ts-auto-guard:type-guard */ | ||
export type Foo = { | ||
[key in PropertyName]: PropertyValueType | ||
}`, | ||
}, | ||
{ | ||
'test.guard.ts': ` | ||
import { PropertyValueType, PropertyName, Foo } from "./test"; | ||
export function isPropertyValueType(obj: any, _argumentName?: string): obj is PropertyValueType { | ||
return ( | ||
typeof obj === "object" && | ||
typeof obj.value === "string" | ||
) | ||
} | ||
export function isPropertyName(obj: any, _argumentName?: string): obj is PropertyName { | ||
return ( | ||
( | ||
obj === "name" || | ||
obj === "value" | ||
) | ||
) | ||
} | ||
export function isFoo(obj: any, _argumentName?: string): obj is Foo { | ||
return ( | ||
typeof obj === "object" && | ||
isPropertyValueType(obj.name) as boolean && | ||
isPropertyValueType(obj.value) as boolean | ||
) | ||
} | ||
`, | ||
} | ||
) | ||
testProcessProject( | ||
'generates type guards for recursive types', | ||
{ | ||
'test.ts': ` | ||
/** @see {isBranch1} ts-auto-guard:type-guard */ | ||
export type Branch1 = Branch1[] | string; | ||
/** @see {isBranch2} ts-auto-guard:type-guard */ | ||
export type Branch2 = { branches: Branch2[] } | string; | ||
/** @see {isBranch3} ts-auto-guard:type-guard */ | ||
export type Branch3 = { branches: Branch3[] } | {branches: Branch3 }[] | string; | ||
`, | ||
}, | ||
{ | ||
'test.guard.ts': ` | ||
import { Branch1, Branch2, Branch3 } from "./test"; | ||
export function isBranch1(obj: any, _argumentName?: string): obj is Branch1 { | ||
return ( | ||
( | ||
typeof obj === "string" || | ||
Array.isArray(obj) && | ||
obj.every((e: any) => | ||
isBranch1(e) as boolean | ||
) | ||
) | ||
) | ||
} | ||
export function isBranch2(obj: any, _argumentName?: string): obj is Branch2 { | ||
return ( | ||
( | ||
typeof obj === "string" || | ||
typeof obj === "object" && | ||
Array.isArray(obj.branches) && | ||
obj.branches.every((e: any) => | ||
isBranch2(e) as boolean | ||
) | ||
) | ||
) | ||
} | ||
export function isBranch3(obj: any, _argumentName?: string): obj is Branch3 { | ||
return ( | ||
( | ||
typeof obj === "string" || | ||
typeof obj === "object" && | ||
Array.isArray(obj.branches) && | ||
obj.branches.every((e: any) => | ||
isBranch3(e) as boolean | ||
) || | ||
Array.isArray(obj) && | ||
obj.every((e: any) => | ||
typeof e === "object" && | ||
isBranch3(e.branches) as boolean | ||
) | ||
) | ||
) | ||
}`, | ||
} | ||
) | ||
testProcessProject( | ||
'generated type guards for discriminated unions', | ||
{ | ||
'test.ts': ` | ||
export type X = { type: 'a', value: number } | { type: 'b', value: string } | ||
`, | ||
}, | ||
{ | ||
'test.guard.ts': ` | ||
import { X } from "./test"; | ||
export function isX(obj: any, _argumentName?: string): obj is X { | ||
return ( | ||
( | ||
typeof obj === "object" && | ||
obj.type === "a" && | ||
typeof obj.value === "number" || | ||
typeof obj === "object" && | ||
obj.type === "b" && | ||
typeof obj.value === "string" | ||
) | ||
) | ||
}`, | ||
}, | ||
{ options: { exportAll: true } } | ||
) | ||
testProcessProject( | ||
'generated type guards for enums', | ||
{ | ||
'test.ts': ` | ||
export enum Types{ | ||
TheGood, | ||
TheBad, | ||
TheTypeSafe | ||
}`, | ||
}, | ||
{ | ||
'test.guard.ts': ` | ||
import { Types } from "./test"; | ||
export function isTypes(obj: any, _argumentName?: string): obj is Types { | ||
return ( | ||
( | ||
obj === Types.TheGood || | ||
obj === Types.TheBad || | ||
obj === Types.TheTypeSafe | ||
) | ||
) | ||
}`, | ||
}, | ||
{ options: { exportAll: true } } | ||
) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
75861
1353