Skip to content

Commit 5d0e1c2

Browse files
author
John Vilk
committed
[TS Decl] Correctly handle optional parameters in the middle of the parameter list by generating variants.
Handles complexities like Git.Commit.create.
1 parent f687656 commit 5d0e1c2

File tree

1 file changed

+101
-20
lines changed

1 file changed

+101
-20
lines changed

generate/lib/write_ts_declarations.js

Lines changed: 101 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,94 @@ var writeTsDecls = function(apiData, path) {
125125
return name.replace(/[\[\]]/g, '');
126126
}
127127

128+
/**
129+
* Given a function, returns a function signature.
130+
*/
131+
function getFunctionSignature(name, fcn, params, isStatic) {
132+
var fcnSig = isStatic ? 'public static' : 'public';
133+
fcnSig += " " + name + "(" +
134+
params.map(function(param) {
135+
var paramName = fixIdentifier(param.name);
136+
// Make each param type a union type if multiple types.
137+
return paramName + (param.optional ? "?" : "") + ": " + (param.types.map(function(type) { return getType(type); }).join(" | "));
138+
}).join(', ') + "): ";
139+
var returnType = fcn.return ? getType(fcn.return.type) : "void";
140+
if (fcn.isAsync) {
141+
returnType = "PromiseLike<" + returnType + ">";
142+
}
143+
return fcnSig + returnType + ";"
144+
}
145+
146+
/**
147+
* Returns an array of parameter permutations to a function.
148+
*/
149+
function getParamPermutations(params) {
150+
// Figure out which parameters are optional.
151+
var optionalParams = []
152+
var nonOptionalAfterOptional = false;
153+
var i;
154+
for (i = 0; i < params.length; i++) {
155+
if (params[i].optional) {
156+
optionalParams.push(i);
157+
} else if (optionalParams.length > 0) {
158+
nonOptionalAfterOptional = true;
159+
}
160+
}
161+
162+
// If no non-optional functions follow optional functions,
163+
// we only need one function signature [common case].
164+
if (!nonOptionalAfterOptional) {
165+
return [params];
166+
} else {
167+
var rv = [];
168+
169+
// Only toggle the ones in the middle. Ignore those at the
170+
// end -- they are benign.
171+
for (i = params.length - 1; i >= 0; i--) {
172+
if (optionalParams[optionalParams.length - 1] === i) {
173+
optionalParams.pop();
174+
} else {
175+
break;
176+
}
177+
}
178+
179+
var numOptionalParams = optionalParams.length;
180+
var state = new Array(numOptionalParams);
181+
for (i = 0; i < numOptionalParams; i++) {
182+
state[i] = 0;
183+
}
184+
outer:
185+
while(true) {
186+
// 'Clone' the params object.
187+
var paramVariant = JSON.parse(JSON.stringify(params));
188+
// Remove 'disabled' optional params, from r2l to avoid messing with
189+
// array indices.
190+
for (i = numOptionalParams - 1; i >= 0; i--) {
191+
var optionalIndex = optionalParams[i];
192+
if (state[i]) {
193+
paramVariant.splice(optionalIndex, 1);
194+
} else {
195+
paramVariant[optionalIndex].optional = false;
196+
}
197+
}
198+
rv.push(paramVariant);
199+
200+
// Add '1' to 'state', from L2R.
201+
var digit = 0;
202+
while (state[digit] === 1) {
203+
if ((digit + 1) < numOptionalParams) {
204+
state[digit] = 0;
205+
digit++;
206+
} else {
207+
break outer;
208+
}
209+
}
210+
state[digit] = 1;
211+
}
212+
return rv.reverse();
213+
}
214+
}
215+
128216
/**
129217
* Converts a function in JSON format into a TypeScript function
130218
* declaration.
@@ -137,33 +225,26 @@ var writeTsDecls = function(apiData, path) {
137225
if (fcn.description !== "") {
138226
jsDoc += fcn.description + "\n";
139227
}
140-
var fcnSig = isStatic ? 'public static' : 'public';
141-
fcnSig += " " + name + "(" +
142-
fcn.params.map(function(param) {
143-
var paramName = fixIdentifier(param.name);
144-
jsDoc += "\n@param " + paramName + " ";
145-
// Apparently param.description can be null, so check that it's not before looking at the contents!
146-
if (param.description && param.description.trim() !== "") {
147-
// Indent secondary lines of the description.
148-
jsDoc += param.description.replace(/\n/g, '\n ') + "\n";
149-
}
150-
// Make each param type a union type if multiple types.
151-
return paramName + (param.optional ? "?" : "") + ": " + (param.types.map(function(type) { return getType(type); }).join(" | "));
152-
}).join(', ') + "): ";
153228

154-
var returnType = fcn.return ? getType(fcn.return.type) : "void";
155-
if (fcn.isAsync) {
156-
returnType = "PromiseLike<" + returnType + ">";
157-
}
229+
var params = fcn.params;
230+
params.map(function(param) {
231+
var paramName = fixIdentifier(param.name);
232+
jsDoc += "\n@param " + paramName + " ";
233+
// Apparently param.description can be null, so check that it's not before looking at the contents!
234+
if (param.description && param.description.trim() !== "") {
235+
// Indent secondary lines of the description.
236+
jsDoc += param.description.replace(/\n/g, '\n ') + "\n";
237+
}
238+
});
239+
158240
var fcnDesc = fcn.return ? fcn.return.description.replace(/\n/g, '\n ') : '';
159241

160242
if (fcnDesc.trim() !== "") {
161243
jsDoc += "\n@return " + fcnDesc;
162244
}
163245

164-
fcnSig += returnType + ";"
165-
166-
return textToJSDoc(jsDoc) + "\n" + fcnSig;
246+
var paramPermutations = getParamPermutations(fcn.params);
247+
return textToJSDoc(jsDoc) + "\n" + paramPermutations.map(function(params) { return getFunctionSignature(name, fcn, params, isStatic); }).join("\n");
167248
}
168249

169250
/**

0 commit comments

Comments
 (0)