ts-auto-guard
Advanced tools
Comparing version 1.0.0-alpha.3 to 1.0.0-alpha.4
@@ -85,2 +85,7 @@ #!/usr/bin/env node | ||
{ | ||
description: 'Generate checks for all exported types, even those not marked with comment', | ||
name: 'export-all', | ||
type: Boolean, | ||
}, | ||
{ | ||
description: 'Path to `tsconfig.json`.', | ||
@@ -124,2 +129,3 @@ name: 'project', | ||
debug: options.debug, | ||
exportAll: options['export-all'], | ||
shortCircuitCondition: options.shortcircuit, | ||
@@ -126,0 +132,0 @@ }, |
import { Project } from 'ts-morph'; | ||
export interface IProcessOptions { | ||
exportAll?: boolean; | ||
shortCircuitCondition?: string; | ||
@@ -4,0 +5,0 @@ debug: boolean; |
142
lib/index.js
@@ -151,4 +151,6 @@ "use strict"; | ||
} | ||
function getTypeGuardName(jsDocs) { | ||
function getTypeGuardName(child, options) { | ||
var e_2, _a, e_3, _b; | ||
var _c; | ||
var jsDocs = child.getJsDocs(); | ||
try { | ||
@@ -158,4 +160,4 @@ for (var jsDocs_1 = __values(jsDocs), jsDocs_1_1 = jsDocs_1.next(); !jsDocs_1_1.done; jsDocs_1_1 = jsDocs_1.next()) { | ||
try { | ||
for (var _c = (e_3 = void 0, __values(doc.getInnerText().split('\n'))), _d = _c.next(); !_d.done; _d = _c.next()) { | ||
var line = _d.value; | ||
for (var _d = (e_3 = void 0, __values(doc.getInnerText().split('\n'))), _e = _d.next(); !_e.done; _e = _d.next()) { | ||
var line = _e.value; | ||
var match = line | ||
@@ -165,3 +167,3 @@ .trim() | ||
if (match !== null) { | ||
var _e = __read(match, 3), typeGuardName = _e[1], command = _e[2]; | ||
var _f = __read(match, 3), typeGuardName = _f[1], command = _f[2]; | ||
if (command !== 'type-guard') { | ||
@@ -178,3 +180,3 @@ reportError("command " + command + " is not supported!"); | ||
try { | ||
if (_d && !_d.done && (_b = _c.return)) _b.call(_c); | ||
if (_e && !_e.done && (_b = _d.return)) _b.call(_d); | ||
} | ||
@@ -192,2 +194,9 @@ finally { if (e_3) throw e_3.error; } | ||
} | ||
if (options.exportAll) { | ||
var t = child.getType(); | ||
var name = (_c = t.getSymbol()) === null || _c === void 0 ? void 0 : _c.getName(); | ||
if (name) { | ||
return 'is' + name; | ||
} | ||
} | ||
return null; | ||
@@ -216,7 +225,7 @@ } | ||
} | ||
function typeUnionConditions(varName, types, addDependency, project, path, arrayDepth, debug) { | ||
function typeUnionConditions(varName, types, addDependency, project, path, arrayDepth, options) { | ||
var conditions = []; | ||
conditions.push.apply(conditions, __spread(types | ||
.map(function (type) { | ||
return typeConditions(varName, type, addDependency, project, path, arrayDepth, true, debug); | ||
return typeConditions(varName, type, addDependency, project, path, arrayDepth, true, options); | ||
}) | ||
@@ -229,3 +238,3 @@ .filter(function (v) { return v !== null; }))); | ||
} | ||
function arrayCondition(varName, arrayType, addDependency, project, path, arrayDepth, debug) { | ||
function arrayCondition(varName, arrayType, addDependency, project, path, arrayDepth, options) { | ||
if (arrayType.getText() === 'never') { | ||
@@ -236,3 +245,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, debug); | ||
var conditions = typeConditions('e', arrayType, addDependency, project, elementPath, arrayDepth + 1, true, options); | ||
if (conditions === null) { | ||
@@ -253,3 +262,3 @@ reportError("No conditions for " + varName + ", with array type " + arrayType.getText()); | ||
} | ||
function objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, debug) { | ||
function objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, options) { | ||
var conditions = []; | ||
@@ -272,6 +281,11 @@ var symbol = type.getSymbol(); | ||
// case of eg. `type Foo = { x: number }` | ||
var docNode = ts_morph_1.TypeGuards.isJSDocableNode(declaration) | ||
? declaration | ||
: declaration.getParentIfKind(ts_morph_1.SyntaxKind.TypeAliasDeclaration) || null; | ||
var typeGuardName = docNode === null ? null : getTypeGuardName(docNode.getJsDocs()); | ||
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) { | ||
@@ -286,7 +300,7 @@ var sourcePath = declaration.getSourceFile().getFilePath(); | ||
if (!useGuard || typeGuardName === null) { | ||
if (!ts_morph_1.TypeGuards.isInterfaceDeclaration(declaration)) { | ||
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, debug); | ||
var condition = typeConditions(varName, baseType, addDependency, project, path, arrayDepth, true, options); | ||
if (condition !== null) { | ||
@@ -299,3 +313,3 @@ conditions.push(condition); | ||
} | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, declaration.getProperties(), addDependency, project, path, arrayDepth, debug))); | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, declaration.getProperties(), addDependency, project, path, arrayDepth, options))); | ||
} | ||
@@ -309,3 +323,3 @@ } | ||
var propertySignatures = properties.map(function (p) { return p.getDeclarations()[0]; }); | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, propertySignatures, addDependency, project, path, arrayDepth, debug))); | ||
conditions.push.apply(conditions, __spread(propertiesConditions(varName, propertySignatures, addDependency, project, path, arrayDepth, options))); | ||
} | ||
@@ -321,6 +335,6 @@ catch (error) { | ||
} | ||
function tupleCondition(varName, type, addDependency, project, path, arrayDepth, debug) { | ||
function tupleCondition(varName, type, addDependency, project, path, arrayDepth, options) { | ||
var types = type.getTupleElements(); | ||
var conditions = types.reduce(function (acc, elementType, i) { | ||
var condition = typeConditions(varName + "[" + i + "]", elementType, addDependency, project, path, arrayDepth, true, debug); | ||
var condition = typeConditions(varName + "[" + i + "]", elementType, addDependency, project, path, arrayDepth, true, options); | ||
if (condition !== null) { | ||
@@ -352,3 +366,3 @@ acc.push(condition); | ||
} | ||
function typeConditions(varName, type, addDependency, project, path, arrayDepth, useGuard, debug) { | ||
function typeConditions(varName, type, addDependency, project, path, arrayDepth, useGuard, options) { | ||
if (type.isNull()) { | ||
@@ -372,12 +386,12 @@ return eq(varName, 'null'); | ||
} | ||
return typeUnionConditions(varName, type.getUnionTypes(), addDependency, project, path, arrayDepth, debug); | ||
return typeUnionConditions(varName, type.getUnionTypes(), addDependency, project, path, arrayDepth, options); | ||
} | ||
if (type.isIntersection()) { | ||
return typeUnionConditions(varName, type.getIntersectionTypes(), addDependency, project, path, arrayDepth, debug); | ||
return typeUnionConditions(varName, type.getIntersectionTypes(), addDependency, project, path, arrayDepth, options); | ||
} | ||
if (type.isArray()) { | ||
return arrayCondition(varName, type.getArrayElementType(), addDependency, project, path, arrayDepth, debug); | ||
return arrayCondition(varName, type.getArrayElementType(), addDependency, project, path, arrayDepth, options); | ||
} | ||
if (isReadonlyArrayType(type)) { | ||
return arrayCondition(varName, getReadonlyArrayType(type), addDependency, project, path, arrayDepth, debug); | ||
return arrayCondition(varName, getReadonlyArrayType(type), addDependency, project, path, arrayDepth, options); | ||
} | ||
@@ -389,6 +403,6 @@ if (isClassType(type)) { | ||
if (type.isObject()) { | ||
return objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, debug); | ||
return objectCondition(varName, type, addDependency, useGuard, project, path, arrayDepth, options); | ||
} | ||
if (type.isTuple()) { | ||
return tupleCondition(varName, type, addDependency, project, path, arrayDepth, debug); | ||
return tupleCondition(varName, type, addDependency, project, path, arrayDepth, options); | ||
} | ||
@@ -400,3 +414,4 @@ if (type.isLiteral()) { | ||
} | ||
function propertyConditions(objName, property, addDependency, project, path, arrayDepth, debug) { | ||
function propertyConditions(objName, property, addDependency, project, path, arrayDepth, options) { | ||
var debug = options.debug; | ||
// working around a bug in ts-simple-ast | ||
@@ -407,3 +422,3 @@ var propertyName = property === undefined ? '(???)' : property.getName(); | ||
var expectedType = property.getType().getText(); | ||
var conditions = typeConditions(varName, property.getType(), addDependency, project, propertyPath, arrayDepth, true, debug); | ||
var conditions = typeConditions(varName, property.getType(), addDependency, project, propertyPath, arrayDepth, true, options); | ||
if (debug) { | ||
@@ -415,13 +430,14 @@ return (conditions && | ||
} | ||
function propertiesConditions(varName, properties, addDependency, project, path, arrayDepth, debug) { | ||
function propertiesConditions(varName, properties, addDependency, project, path, arrayDepth, options) { | ||
return properties | ||
.map(function (prop) { | ||
return propertyConditions(varName, prop, addDependency, project, path, arrayDepth, debug); | ||
return propertyConditions(varName, prop, addDependency, project, path, arrayDepth, options); | ||
}) | ||
.filter(function (v) { return v !== null; }); | ||
} | ||
function generateTypeGuard(functionName, typeName, type, addDependency, project, shortCircuitCondition, debug) { | ||
function generateTypeGuard(functionName, typeName, type, addDependency, project, options) { | ||
var debug = options.debug, shortCircuitCondition = options.shortCircuitCondition; | ||
var defaultArgumentName = lodash_1.lowerFirst(typeName); | ||
var conditions = typeConditions('obj', type, addDependency, project, '${argumentName}', // tslint:disable-line:no-invalid-template-strings | ||
0, false, debug); | ||
0, false, options); | ||
var secondArgument = debug | ||
@@ -487,35 +503,39 @@ ? "argumentName: string = \"" + defaultArgumentName + "\"" | ||
project.getSourceFiles().forEach(function (sourceFile) { | ||
var e_4, _a, e_5, _b; | ||
var dependencies = new Map(); | ||
var addDependency = createAddDependency(dependencies); | ||
var functions = sourceFile | ||
.getChildAtIndex(0) | ||
.getChildren() | ||
.reduce(function (acc, child) { | ||
if (!ts_morph_1.TypeGuards.isJSDocableNode(child)) { | ||
return acc; | ||
} | ||
var typeGuardName = getTypeGuardName(child.getJsDocs()); | ||
if (typeGuardName === null) { | ||
return acc; | ||
} | ||
if (!ts_morph_1.TypeGuards.isExportableNode(child)) { | ||
reportError("Must be exportable:\n\n" + child.getText() + "\n"); | ||
return acc; | ||
} | ||
if (ts_morph_1.TypeGuards.isEnumDeclaration(child) || | ||
ts_morph_1.TypeGuards.isInterfaceDeclaration(child) || | ||
ts_morph_1.TypeGuards.isTypeAliasDeclaration(child)) { | ||
if (!child.isExported()) { | ||
reportError("Node must be exported:\n\n" + child.getText() + "\n"); | ||
var functions = []; | ||
var exports = Array.from(sourceFile.getExportedDeclarations().values()); | ||
try { | ||
for (var exports_1 = __values(exports), exports_1_1 = exports_1.next(); !exports_1_1.done; exports_1_1 = exports_1.next()) { | ||
var exp = exports_1_1.value; | ||
try { | ||
for (var exp_1 = (e_5 = void 0, __values(exp)), exp_1_1 = exp_1.next(); !exp_1_1.done; exp_1_1 = exp_1.next()) { | ||
var singleExport = exp_1_1.value; | ||
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()); | ||
} | ||
} | ||
} | ||
} | ||
acc.push(generateTypeGuard(typeGuardName, child.getName(), child.getType(), addDependency, project, options.shortCircuitCondition, options.debug)); | ||
var exportName = child.getName(); | ||
addDependency(sourceFile, exportName, child.isDefaultExport()); | ||
catch (e_5_1) { e_5 = { error: e_5_1 }; } | ||
finally { | ||
try { | ||
if (exp_1_1 && !exp_1_1.done && (_b = exp_1.return)) _b.call(exp_1); | ||
} | ||
finally { if (e_5) throw e_5.error; } | ||
} | ||
} | ||
else { | ||
reportError("Unsupported:\n\n" + child.getText() + "\n"); | ||
return acc; | ||
} | ||
catch (e_4_1) { e_4 = { error: e_4_1 }; } | ||
finally { | ||
try { | ||
if (exports_1_1 && !exports_1_1.done && (_a = exports_1.return)) _a.call(exports_1); | ||
} | ||
return acc; | ||
}, []); | ||
finally { if (e_4) throw e_4.error; } | ||
} | ||
if (functions.length > 0) { | ||
@@ -522,0 +542,0 @@ if (options.debug) { |
{ | ||
"name": "ts-auto-guard", | ||
"version": "1.0.0-alpha.3", | ||
"version": "1.0.0-alpha.4", | ||
"description": "Generate type guard functions from TypeScript interfaces", | ||
@@ -5,0 +5,0 @@ "homepage": "https://github.com/rhys-vdw/ts-auto-guard", |
@@ -27,17 +27,4 @@ # ts-auto-guard | ||
Annotate interfaces in your project. ts-auto-guard will generate guards only for interfaces with a `@see {name} ts-auto-guard:type-guard` JSDoc tag. | ||
Specify which types to process (see below) and run the CLI tool in the same folder as your project's `tsconfig.json` (optionally passing in paths to the files you'd like it to parse). | ||
```ts | ||
// my-project/Person.ts | ||
/** @see {isPerson} ts-auto-guard:type-guard */ | ||
export interface Person { | ||
name: string | ||
age?: number | ||
children: Person[] | ||
} | ||
``` | ||
Run the CLI tool in the same folder as your project's `tsconfig.json` (optionally passing in paths to the files you'd like it to parse). | ||
```sh | ||
@@ -84,2 +71,24 @@ $ ts-auto-guard ./my-project/Person.ts | ||
## Specifying which types to process | ||
###Specify with annotation | ||
Annotate interfaces in your project or pass. ts-auto-guard will generate guards only for interfaces with a `@see {name} ts-auto-guard:type-guard` JSDoc tag. | ||
```ts | ||
// my-project/Person.ts | ||
/** @see {isPerson} ts-auto-guard:type-guard */ | ||
export interface Person { // !do not forget to export - only exported types are processed | ||
name: string | ||
age?: number | ||
children: Person[] | ||
} | ||
``` | ||
###Process all types | ||
Use `--export-all` parameter to process all exported types: | ||
``` | ||
$ ts-auto-guard --export-all | ||
``` | ||
## Debug mode | ||
@@ -86,0 +95,0 @@ |
@@ -108,2 +108,21 @@ import { each, pull } from 'lodash' | ||
testProcessProject( | ||
'generates type guards for empty object if exportAll is true', | ||
{ | ||
'test.ts': ` | ||
export interface Empty {}`, | ||
}, | ||
{ | ||
'test.guard.ts': ` | ||
import { Empty } from "./test"; | ||
export function isEmpty(obj: any, _argumentName?: string): obj is Empty { | ||
return ( | ||
typeof obj === "object" | ||
) | ||
}`, | ||
}, | ||
{ options: { exportAll: true, debug: false } } | ||
) | ||
testProcessProject( | ||
'generates type guards for boolean', | ||
@@ -110,0 +129,0 @@ { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
69094
1175
133