Skip to content

Commit f0e1e31

Browse files
authored
Merge pull request #159 from chomosuke/master
Check if object is a function if the type idicates so
2 parents 0320b37 + 2808d73 commit f0e1e31

File tree

2 files changed

+101
-10
lines changed

2 files changed

+101
-10
lines changed

src/index.ts

+36-10
Original file line numberDiff line numberDiff line change
@@ -259,11 +259,13 @@ function arrayCondition(
259259
)
260260
}
261261

262-
function objectTypeCondition(varName: string): string {
263-
return ors(
264-
ands(ne(varName, 'null'), typeOf(varName, 'object')),
265-
typeOf(varName, 'function')
266-
)
262+
function objectTypeCondition(varName: string, callable: boolean): string {
263+
return callable
264+
? typeOf(varName, 'function')
265+
: ors(
266+
ands(ne(varName, 'null'), typeOf(varName, 'object')),
267+
typeOf(varName, 'function')
268+
)
267269
}
268270

269271
function objectCondition(
@@ -298,6 +300,27 @@ function objectCondition(
298300
return null
299301
}
300302

303+
const callable = type.getCallSignatures().length !== 0
304+
305+
if (callable) {
306+
// emit warning
307+
const suppressComment = 'ts-auto-guard-suppress function-type'
308+
const commentsBefore = declaration.getLeadingCommentRanges()
309+
const commentBefore = commentsBefore[commentsBefore.length - 1]
310+
if (
311+
commentBefore === undefined ||
312+
!commentBefore.getText().includes(suppressComment)
313+
) {
314+
console.warn(
315+
`
316+
It seems that ${varName} has a function type.
317+
Note that it is impossible to check if a function has the correct signiture and return type at runtime.
318+
To disable this warning, put comment "${suppressComment}" before the declaration.
319+
`
320+
)
321+
}
322+
}
323+
301324
if (type.isInterface()) {
302325
if (!Node.isInterfaceDeclaration(declaration)) {
303326
throw new TypeError(
@@ -322,11 +345,14 @@ function objectCondition(
322345
}
323346
})
324347
if (conditions.length === 0) {
325-
conditions.push(objectTypeCondition(varName))
348+
conditions.push(objectTypeCondition(varName, callable))
326349
}
327-
const properties = declaration
328-
.getProperties()
329-
.map(p => ({ name: p.getName(), type: p.getType() }))
350+
351+
// getProperties does not include methods like `foo(): void`
352+
const properties = [
353+
...declaration.getProperties(),
354+
...declaration.getMethods(),
355+
].map(p => ({ name: p.getName(), type: p.getType() }))
330356
conditions.push(
331357
...propertiesConditions(
332358
varName,
@@ -360,7 +386,7 @@ function objectCondition(
360386
)
361387
}
362388
} else {
363-
conditions.push(objectTypeCondition(varName))
389+
conditions.push(objectTypeCondition(varName, callable))
364390
// Get object literal properties...
365391
try {
366392
const properties = type.getProperties()

tests/generate.ts

+65
Original file line numberDiff line numberDiff line change
@@ -1395,3 +1395,68 @@ testProcessProject(
13951395
`,
13961396
}
13971397
)
1398+
1399+
testProcessProject(
1400+
'Check if any callable properties is a function',
1401+
// should also emit a warning about how it is not possible to check function type at runtime.
1402+
{
1403+
'test.ts': `
1404+
/** @see {isTestType} ts-auto-guard:type-guard */
1405+
export interface TestType {
1406+
test: (() => void)
1407+
// ts-auto-guard-suppress function-type
1408+
test2(someArg: number): boolean
1409+
// some other comments
1410+
test3: {
1411+
(someArg: string): number
1412+
test3Arg: number;
1413+
}
1414+
}
1415+
`,
1416+
},
1417+
{
1418+
'test.ts': null,
1419+
'test.guard.ts': `
1420+
import { TestType } from "./test";
1421+
1422+
export function isTestType(obj: any, _argumentName?: string): obj is TestType {
1423+
return (
1424+
(obj !== null &&
1425+
typeof obj === "object" ||
1426+
typeof obj === "function") &&
1427+
typeof obj.test === "function" &&
1428+
typeof obj.test3 === "function" &&
1429+
typeof obj.test3.test3Arg === "number" &&
1430+
typeof obj.test2 === "function"
1431+
)
1432+
}
1433+
`,
1434+
}
1435+
)
1436+
1437+
testProcessProject(
1438+
'Check if callable interface is a function',
1439+
// should also emit a warning about how it is not possible to check function type at runtime.
1440+
{
1441+
'test.ts': `
1442+
/** @see {isTestType} ts-auto-guard:type-guard */
1443+
export interface TestType {
1444+
(someArg: string): number
1445+
arg: number;
1446+
}
1447+
`,
1448+
},
1449+
{
1450+
'test.ts': null,
1451+
'test.guard.ts': `
1452+
import { TestType } from "./test";
1453+
1454+
export function isTestType(obj: any, _argumentName?: string): obj is TestType {
1455+
return (
1456+
typeof obj === "function" &&
1457+
typeof obj.arg === "number"
1458+
)
1459+
}
1460+
`,
1461+
}
1462+
)

0 commit comments

Comments
 (0)