Skip to content

Commit bac3d43

Browse files
TakehiroTadaclaude
andcommitted
refactor: replace type assertions with type guards and narrowing
- service.mts: remove all `as` casts by using ts.isArrowFunction, ts.isReturnStatement, ts.isCallExpression type guards for proper narrowing - createExports.mts: use reduce generic, ts.isTypeLiteralNode, ts.isPropertySignature type guards, and type predicate filter - createUseQuery.mts: add comments explaining unavoidable assertions on factory-created nodes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9176ef3 commit bac3d43

File tree

3 files changed

+21
-24
lines changed

3 files changed

+21
-24
lines changed

src/createExports.mts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export const createExports = ({
2424
initialPageParam: string;
2525
}) => {
2626
const { methods } = service;
27-
const methodDataNames = methods.reduce(
27+
const methodDataNames = methods.reduce<Record<string, string>>(
2828
(acc, data) => {
2929
const methodName = data.method.getName();
3030
acc[`${capitalizeFirstLetter(methodName)}Data`] = methodName;
3131
return acc;
3232
},
33-
{} as { [key: string]: string },
33+
{},
3434
);
3535
const modelsFile = project
3636
.getSourceFiles?.()
@@ -46,14 +46,13 @@ export const createExports = ({
4646
if (ts.isTypeAliasDeclaration(node) && methodDataNames[key] !== undefined) {
4747
// get the type alias declaration
4848
const typeAliasDeclaration = node.type;
49-
if (typeAliasDeclaration.kind === ts.SyntaxKind.TypeLiteral) {
50-
const query = (typeAliasDeclaration as ts.TypeLiteralNode).members.find(
51-
(m) =>
52-
m.kind === ts.SyntaxKind.PropertySignature &&
53-
m.name?.getText() === "query",
49+
if (ts.isTypeLiteralNode(typeAliasDeclaration)) {
50+
const query = typeAliasDeclaration.members.find(
51+
(m): m is ts.PropertySignature =>
52+
ts.isPropertySignature(m) && m.name?.getText() === "query",
5453
);
5554
if (query) {
56-
const queryType = (query as ts.PropertySignature).type;
55+
const queryType = query.type;
5756
const members =
5857
queryType && ts.isTypeLiteralNode(queryType)
5958
? queryType.members
@@ -148,7 +147,7 @@ export const createExports = ({
148147

149148
const infiniteQueriesExports = allQueries
150149
.flatMap(({ infiniteQueryHook }) => [infiniteQueryHook])
151-
.filter(Boolean) as ts.VariableStatement[];
150+
.filter((x): x is ts.VariableStatement => x != null);
152151

153152
const suspenseQueries = allQueries.flatMap(({ suspenseQueryHook }) => [
154153
suspenseQueryHook,

src/createUseQuery.mts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@ function createQueryHook({
225225
const isInfiniteQuery = queryString === "useInfiniteQuery";
226226
const isSuspenseQuery = queryString === "useSuspenseQuery";
227227

228+
// ts.TypeParameterDeclaration.default is ts.TypeNode | undefined.
229+
// We know it's a TypeReferenceNode with an Identifier typeName because we created it
230+
// via ts.factory in createApiResponseType, but TypeScript cannot infer the specific subtype.
228231
const responseDataTypeRef = responseDataType.default as ts.TypeReferenceNode;
229232
const responseDataTypeIdentifier =
230233
responseDataTypeRef.typeName as ts.Identifier;

src/service.mts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ export function getMethodsFromService(node: SourceFile): FunctionDescription[] {
3232
const declarations = statement.getDeclarations();
3333
return declarations.some((decl) => {
3434
const initializer = decl.getInitializer();
35-
return (
36-
initializer && ts.isArrowFunction(initializer.compilerNode as ts.Node)
37-
);
35+
return initializer && ts.isArrowFunction(initializer.compilerNode);
3836
});
3937
});
4038

@@ -45,42 +43,39 @@ export function getMethodsFromService(node: SourceFile): FunctionDescription[] {
4543
if (!initializer) {
4644
throw new Error("Initializer not found");
4745
}
48-
const arrowFunction = initializer.compilerNode as ts.ArrowFunction;
49-
if (!ts.isArrowFunction(arrowFunction)) {
46+
const compilerNode = initializer.compilerNode;
47+
if (!ts.isArrowFunction(compilerNode)) {
5048
throw new Error("Arrow function not found");
5149
}
52-
const arrowBody = arrowFunction.body;
50+
const arrowBody = compilerNode.body;
5351

5452
// Find the call expression - either from block's return statement or direct expression
55-
let callExpression: ts.CallExpression;
53+
let callExpression: ts.Expression;
5654
let methodBlockNode: ts.Block | undefined;
5755

5856
if (ts.isBlock(arrowBody)) {
5957
// Old style: arrow function with block body
6058
methodBlockNode = arrowBody;
61-
const foundReturnStatement = arrowBody.statements.find(
62-
(s) => s.kind === ts.SyntaxKind.ReturnStatement,
63-
);
64-
if (!foundReturnStatement) {
59+
const returnStatement = arrowBody.statements.find(ts.isReturnStatement);
60+
if (!returnStatement) {
6561
throw new Error("Return statement not found");
6662
}
67-
const returnStatement = foundReturnStatement as ts.ReturnStatement;
6863
if (!returnStatement.expression) {
6964
throw new Error("Call expression not found");
7065
}
71-
callExpression = returnStatement.expression as ts.CallExpression;
66+
callExpression = returnStatement.expression;
7267
} else {
7368
// New style: arrow function with expression body (no block)
7469
// The body is a call expression like: (options?.client ?? client).post<...>({...})
75-
callExpression = arrowBody as ts.CallExpression;
70+
callExpression = arrowBody;
7671
}
7772

7873
// Navigate to find the HTTP method name (get, post, put, delete, etc.)
7974
let httpMethodName: string | undefined;
8075
if (ts.isCallExpression(callExpression)) {
8176
const expr = callExpression.expression;
8277
if (ts.isPropertyAccessExpression(expr)) {
83-
httpMethodName = (expr.name as ts.Identifier).text;
78+
httpMethodName = expr.name.text;
8479
}
8580
}
8681

0 commit comments

Comments
 (0)