From cb76b2c1d8c63fa938179d6898a353021eb54755 Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sat, 3 Feb 2024 23:38:02 -0800 Subject: [PATCH 1/7] Change `self` to `this` --- src/xtgen.ts | 5 ++++- xtgen.mjs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/xtgen.ts b/src/xtgen.ts index 0d8f0cd..b0f1872 100644 --- a/src/xtgen.ts +++ b/src/xtgen.ts @@ -292,11 +292,14 @@ function isApiFunc( // Sanitizes name function getName(name: string) { - let modifiedName = name.replace('...', 'args'); + // Special case: arguments + let modifiedName = name.replace(/^\.\.\.$/, 'args'); // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; } + // Special case: Lua's `self` variable + modifiedName = modifiedName.replace(/^self$/, 'this'); // Sanitize type name, allow alpha-numeric and underscore modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); if (modifiedName !== name) { diff --git a/xtgen.mjs b/xtgen.mjs index c0daa5e..3f57db3 100644 --- a/xtgen.mjs +++ b/xtgen.mjs @@ -256,11 +256,14 @@ function isApiFunc(entry) { } // Sanitizes name function getName(name) { - let modifiedName = name.replace('...', 'args'); + // Special case: arguments + let modifiedName = name.replace(/^\.\.\.$/, 'args'); // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; } + // Special case: Lua's `self` variable + modifiedName = modifiedName.replace(/^self$/, 'this'); // Sanitize type name, allow alpha-numeric and underscore modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); if (modifiedName !== name) { From 38c8757e52e5499896a2f6a6afb299b183037887 Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sat, 3 Feb 2024 23:38:41 -0800 Subject: [PATCH 2/7] Expand list of reserved words --- src/xtgen.ts | 12 ++++++++++++ xtgen.mjs | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/xtgen.ts b/src/xtgen.ts index b0f1872..1c243bd 100644 --- a/src/xtgen.ts +++ b/src/xtgen.ts @@ -16,6 +16,8 @@ const HEADER = `/** @noSelfInFile */ // Invalid names in TypeScript const INVALID_NAMES = [ + 'any', + 'boolean', 'break', 'case', 'catch', @@ -27,6 +29,7 @@ const INVALID_NAMES = [ 'delete', 'do', 'else', + 'enum', 'export', 'extends', 'false', @@ -34,12 +37,20 @@ const INVALID_NAMES = [ 'for', 'function', 'if', + 'implements', 'import', 'in', 'instanceof', + 'interface', + 'let', 'new', 'null', + 'package', + 'private', + 'protected', + 'public', 'return', + 'static', 'super', 'switch', 'this', @@ -47,6 +58,7 @@ const INVALID_NAMES = [ 'true', 'try', 'typeof', + 'undefined', 'var', 'void', 'while', diff --git a/xtgen.mjs b/xtgen.mjs index 3f57db3..7600f77 100644 --- a/xtgen.mjs +++ b/xtgen.mjs @@ -13,6 +13,8 @@ const HEADER = `/** @noSelfInFile */ /// \n`; // Invalid names in TypeScript const INVALID_NAMES = [ + 'any', + 'boolean', 'break', 'case', 'catch', @@ -24,6 +26,7 @@ const INVALID_NAMES = [ 'delete', 'do', 'else', + 'enum', 'export', 'extends', 'false', @@ -31,12 +34,20 @@ const INVALID_NAMES = [ 'for', 'function', 'if', + 'implements', 'import', 'in', 'instanceof', + 'interface', + 'let', 'new', 'null', + 'package', + 'private', + 'protected', + 'public', 'return', + 'static', 'super', 'switch', 'this', @@ -44,6 +55,7 @@ const INVALID_NAMES = [ 'true', 'try', 'typeof', + 'undefined', 'var', 'void', 'while', From b6015989163e49e2dadab294290400a53879023c Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sat, 3 Feb 2024 23:39:16 -0800 Subject: [PATCH 3/7] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f8c3cf..8cf5d48 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tsd-ext-type-gen", - "version": "1.0.4", + "version": "1.0.5", "description": "TypeScript definitions generator that parses script APIs from Defold extensions", "repository": { "type": "git", From c7c20d184841d0447eb25f46370aeb13597119c0 Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sat, 3 Feb 2024 23:46:21 -0800 Subject: [PATCH 4/7] Add check for special param names --- src/xtgen.ts | 26 ++++++++++++++++---------- xtgen.mjs | 25 +++++++++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/xtgen.ts b/src/xtgen.ts index 1c243bd..1446e60 100644 --- a/src/xtgen.ts +++ b/src/xtgen.ts @@ -303,15 +303,19 @@ function isApiFunc( } // Sanitizes name -function getName(name: string) { - // Special case: arguments - let modifiedName = name.replace(/^\.\.\.$/, 'args'); +function getName(name: string, isParam: boolean) { + let modifiedName = name; + // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; } - // Special case: Lua's `self` variable - modifiedName = modifiedName.replace(/^self$/, 'this'); + if (isParam) { + // Special case: arguments + modifiedName = modifiedName.replace(/^\.\.\.$/, 'args'); + // Special case: Lua's `self` variable + modifiedName = modifiedName.replace(/^self$/, 'this'); + } // Sanitize type name, allow alpha-numeric and underscore modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); if (modifiedName !== name) { @@ -412,7 +416,7 @@ function getReturnComments( function getParamComments(parameters: ScriptApiParameter[], newDesc: string) { parameters.forEach((param) => { - const name = param.name ? getName(param.name) : ''; + const name = param.name ? getName(param.name, true) : ''; if (name) { newDesc += `\n * @param`; if (param.type) { @@ -468,7 +472,7 @@ function generateTableDefinition( details: ScriptDetails, start = false, ): string { - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name ? getName(entry.name, false) : DEFAULT_NAME_IF_BLANK; let tableDeclaration = `export namespace ${name} {\n`; if (start) { tableDeclaration = details.isLua @@ -497,13 +501,15 @@ function generateFunctionDefinition( return `(${parameters}) => ${returnType}`; } else { const comment = getComments(entry); - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name + ? getName(entry.name, false) + : DEFAULT_NAME_IF_BLANK; return `${comment}export function ${name}(${parameters}): ${returnType};\n`; } } function getParameterDefinition(param: ScriptApiParameter): string { - const name = param.name ? getName(param.name) : DEFAULT_NAME_IF_BLANK; + const name = param.name ? getName(param.name, true) : DEFAULT_NAME_IF_BLANK; const optional = param.optional ? '?' : ''; let type = getType(param.type, 'param'); @@ -543,7 +549,7 @@ function getReturnType( // Function to generate TypeScript definitions for ScriptApiEntry function generateEntryDefinition(entry: ScriptApiEntry): string { - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name ? getName(entry.name, false) : DEFAULT_NAME_IF_BLANK; const varType = isAllUppercase(name) ? 'const' : 'let'; const type = getType(entry.type, 'return'); const comment = getComments(entry); diff --git a/xtgen.mjs b/xtgen.mjs index 7600f77..850b03f 100644 --- a/xtgen.mjs +++ b/xtgen.mjs @@ -267,15 +267,18 @@ function isApiFunc(entry) { ); } // Sanitizes name -function getName(name) { - // Special case: arguments - let modifiedName = name.replace(/^\.\.\.$/, 'args'); +function getName(name, isParam) { + let modifiedName = name; // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; } - // Special case: Lua's `self` variable - modifiedName = modifiedName.replace(/^self$/, 'this'); + if (isParam) { + // Special case: arguments + modifiedName = modifiedName.replace(/^\.\.\.$/, 'args'); + // Special case: Lua's `self` variable + modifiedName = modifiedName.replace(/^self$/, 'this'); + } // Sanitize type name, allow alpha-numeric and underscore modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); if (modifiedName !== name) { @@ -357,7 +360,7 @@ function getReturnComments(returnObj, newDesc) { } function getParamComments(parameters, newDesc) { parameters.forEach((param) => { - const name = param.name ? getName(param.name) : ''; + const name = param.name ? getName(param.name, true) : ''; if (name) { newDesc += `\n * @param`; if (param.type) { @@ -403,7 +406,7 @@ function getExampleComments(examples, newDesc) { // Main Functions // Function to generate TypeScript definitions for ScriptApiTable function generateTableDefinition(entry, details, start = false) { - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name ? getName(entry.name, false) : DEFAULT_NAME_IF_BLANK; let tableDeclaration = `export namespace ${name} {\n`; if (start) { tableDeclaration = details.isLua @@ -426,12 +429,14 @@ function generateFunctionDefinition(entry, isParam) { return `(${parameters}) => ${returnType}`; } else { const comment = getComments(entry); - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name + ? getName(entry.name, false) + : DEFAULT_NAME_IF_BLANK; return `${comment}export function ${name}(${parameters}): ${returnType};\n`; } } function getParameterDefinition(param) { - const name = param.name ? getName(param.name) : DEFAULT_NAME_IF_BLANK; + const name = param.name ? getName(param.name, true) : DEFAULT_NAME_IF_BLANK; const optional = param.optional ? '?' : ''; let type = getType(param.type, 'param'); if (type === KNOWN_TYPES['FUNCTION']) { @@ -465,7 +470,7 @@ function getReturnType(returnObj) { } // Function to generate TypeScript definitions for ScriptApiEntry function generateEntryDefinition(entry) { - const name = entry.name ? getName(entry.name) : DEFAULT_NAME_IF_BLANK; + const name = entry.name ? getName(entry.name, false) : DEFAULT_NAME_IF_BLANK; const varType = isAllUppercase(name) ? 'const' : 'let'; const type = getType(entry.type, 'return'); const comment = getComments(entry); From 23fd9a27d3c9818704687ec8cfa72ccba64f2665 Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sat, 3 Feb 2024 23:54:47 -0800 Subject: [PATCH 5/7] Add a check for numerical starts --- src/xtgen.ts | 13 +++++++++++-- xtgen.mjs | 9 +++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/xtgen.ts b/src/xtgen.ts index 1446e60..642cb06 100644 --- a/src/xtgen.ts +++ b/src/xtgen.ts @@ -310,15 +310,24 @@ function getName(name: string, isParam: boolean) { if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; } + if (isParam) { // Special case: arguments modifiedName = modifiedName.replace(/^\.\.\.$/, 'args'); // Special case: Lua's `self` variable modifiedName = modifiedName.replace(/^self$/, 'this'); } - // Sanitize type name, allow alpha-numeric and underscore + + // Sanitize type name: allow only alpha-numeric, underscore, dollar sign modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); - if (modifiedName !== name) { + + // If the first character is a number, add a dollar sign to start + if (/^\d/.test(modifiedName)) { + modifiedName = '$' + modifiedName; + } + + // If we're modifying a function name, not a parameter, give a warning + if (!isParam && modifiedName !== name) { console.warn(`Modifying invalid name "${name}" to "${modifiedName}"`); } diff --git a/xtgen.mjs b/xtgen.mjs index 850b03f..6800492 100644 --- a/xtgen.mjs +++ b/xtgen.mjs @@ -279,9 +279,14 @@ function getName(name, isParam) { // Special case: Lua's `self` variable modifiedName = modifiedName.replace(/^self$/, 'this'); } - // Sanitize type name, allow alpha-numeric and underscore + // Sanitize type name: allow only alpha-numeric, underscore, dollar sign modifiedName = modifiedName.replace(/[^a-zA-Z0-9_$]/g, '_'); - if (modifiedName !== name) { + // If the first character is a number, add a dollar sign to start + if (/^\d/.test(modifiedName)) { + modifiedName = '$' + modifiedName; + } + // If we're modifying a function name, not a parameter, give a warning + if (!isParam && modifiedName !== name) { console.warn(`Modifying invalid name "${name}" to "${modifiedName}"`); } return modifiedName; From b4512eef0abab6089af6e28c3557f39a575b8fb1 Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sun, 4 Feb 2024 00:08:10 -0800 Subject: [PATCH 6/7] Add more fuzz --- tests/fuzz.script_api | 103 +++++++++++++++++++++++++++++------------- tests/game.project | 2 +- 2 files changed, 72 insertions(+), 33 deletions(-) diff --git a/tests/fuzz.script_api b/tests/fuzz.script_api index 8c4f9ea..6a11838 100644 --- a/tests/fuzz.script_api +++ b/tests/fuzz.script_api @@ -1,39 +1,78 @@ -- name: "Fuzz!ngExtens!on" - type: "t@ble" - desc: "Extension for Fuzz!ng JavaScript Code" +- name: Fuzz!ngExtens!on + type: t@ble + desc: Extension for Fuzz!ng JavaScript Code members: - - name: "fuzzC*de" - type: "function" - desc: "Fuzzes the provided JavaScript code." + - name: fuzzC*de + type: function + desc: Fuzzes the provided JavaScript code. parameters: - - name: "c*de" - type: "str!ng" - desc: "The JavaScript code to be fuzzed. Must be a valid str!ng." + - name: ... + desc: The JavaScript code to be fuzzed. returns: - - name: "fuzz?dC*de" - type: "s*ring" - desc: "The fuzzed JavaScript code." + - name: fuzz?dC*de + type: s*ring + desc: The fuzzed JavaScript code. examples: - - desc: "Fuzz!ng a sample JavaScript code" - - - name: "/*gen&rate!nval!dInput" - type: "function" - desc: "Generates an !nval!d JavaScript !nput for test!ng." + - desc: Fuzz!ng a sample JavaScript code + - name: /*gen&rate!nval!dInput + type: function + desc: Generates an !nval!d JavaScript !nput for test!ng. parameters: - - name: "//c*de" - type: "str!ng|n&mber|h@sh" - desc: "Optional JavaScript code to be fuzzed. Must be a valid str!ng." - - name: "t3+st" - type: "s=r!ng" - - name: "f%zz" - type: "str&ng" - desc: "/* */ //" + - name: self + type: any + - name: //c*de + type: str!ng|n&mber|h@sh + desc: Optional JavaScript code to be fuzzed! + - name: t3+st + type: s=r!ng + - name: f%zz + type: str&ng + desc: /* */ // returns: - - name: "!nval!dInput" - type: "st.r!ng" - desc: "The generated !nval!d JavaScript !nput." + - name: '!nval!dInput' + type: st.r!ng + desc: The generated !nval!d JavaScript !nput. examples: - - desc: "Generating !nval!d JavaScript input" - - - name: "/*C&NST@NT" - type: "f#nct!on" + - desc: Generating !nval!d JavaScript input + - name: /*C&NST@NT + type: f#nct!on + - name: 1invalid + - name: class + - name: const + - name: break + - name: let's_try_this + - name: 123_variable + - name: true + - name: false + - name: function + - name: typeof + - name: undefined + - name: for + - name: in + - name: instanceof + - name: switch + - name: case + - name: try + - name: catch + - name: throw + - name: package + - name: private + - name: protected + - name: public + - name: return + - name: static + - name: super + - name: this + - name: while + - name: with + - name: yield + - name: delete + - name: debugger + - name: else + - name: enum + - name: export + - name: extends + - name: finally + - name: if + - name: implements + - name: import diff --git a/tests/game.project b/tests/game.project index 12f76cd..48c41f0 100644 --- a/tests/game.project +++ b/tests/game.project @@ -1,7 +1,7 @@ [project] title = Test ; Value from fuzz.script_api as a zip -dependencies#0 = data:application/zip;base64,UEsDBBQACAAIADtMPlgAAAAAAAAAAJ8EAAAPACAAZnV6ei5zY3JpcHRfYXBpVVQNAAcCM7llAzO5ZQIzuWV1eAsAAQT1AQAABBQAAACllNFqgzAUhu/7FMeOeeFovdhdYawg29hg3UX7Aqk5tgGNkhxLJ334JWq7KNoVlitDzjnfn/w/zkCyDBcwfS2rypO7lyOh1F4upxMA+i7sES23Kdo9Rx2bfVMjcglJrqBthA92YOtYiYIgynldn2G2RaUX5hNgdiYlpiEKmgq7WkpSyphEA7arhdnxqIH2CIXKD4Ijd1GxQc3PLQVTBkEXZAcbO0gHq0kZ9e5BC94YYg8ElMMWwV4A+Rw+S012z+DAUsGhGXVRo5BKJYek2AHPPBoRFCgxKqhBjz4AHllWpNhhOu9obWKg65r+jOmk61IY7FD6ihF60lzP4++yKOk2z95Qou3UwCS07S7Ps6Pq8Jgact/sqoNheNXDk/Rt3E77pd4PPN9XYZWy9H+mOnLo8UHTkJqnbqJ+bb+vqmH1/qDhYQBBCGH4d6CGLOoQ5ldCvmvd4qNW3ZKv1nMT3aExolHWj1jkr9ab5WrTj9WdiVX9D/oBUEsHCN+ExiB1AQAAnwQAAFBLAQIUAxQACAAIADtMPljfhMYgdQEAAJ8EAAAPACAAAAAAAAAAAACkgQAAAABmdXp6LnNjcmlwdF9hcGlVVA0ABwIzuWUDM7llAjO5ZXV4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAF0AAADSAQAAAAA= +dependencies#0 = data:application/zip;base64,UEsDBBQAAAAIAG+/Q1ieTyB9KAIAAI0HAAAPAAAAZnV6ei5zY3JpcHRfYXBpjZPFluswDIb3F97BGWbcXWYm2s9xbDXxGUfOsVXuw9+4HCUDu+ar9P8iHwuUBTwRH7qjUYLZ+wEBhsTh4wdC0LCs/qFXqYX4qSGoJ2IWYRyKjvOLNPFF9uRf5U1J4q3T0/ACihR8eBJ/C7Hw6VQJbw80zOjMI1JUVGnO6dwqikMQlIMovesZDXrdSFVGJ4uMUvpKn1aG66YnJ/O4dfV/OXA1QU6kMK0R9DLFA3U9tunGwJd61c56S+HAG8xabWcGN/cCA1mUFmqOs+zlvKUI0xiuUR/26UEGuOMlQYI9aRP9Gcsu3Wf0HwEh5gUhUcyT172SKDS9AIJAVUX3W0QA22mOSuKwGXp6qtrnSr6ym+BOvK5J/irkzRn/LGNH0t6636TpSZeHgVosn0fLlvVvj0atBe60Lf70QBycVm3dfVa769vabXM48clNt5XNN6dvXNt97my+f4NZm4yJMvzU3u78+Pvv1Y9/7Lw2UVHisB59bqKo0XWqrAyBIYeBOaUe5HUdWaDdcEV+eEW5Cczq4vKqJ72RqWWvg3yXkY60gaP586jT2Jvr1FkXNXQMgmYCzteBQf4dSKJq6IW+IZWzccjQaGLIQxpZlHvXr6NSqmuZAYPe9CQ1oCNQBJrhbmqNqrPZRbMuSBKPC90S2FCae+vnxgJDhlhnQwOWFabBAgFnaTfLuCc0lg3YLRgZlM4TZwSoA1uzQWkt24VhKzXxuRWAFDifufwHUEsBAj8AFAAAAAgAb79DWJ5PIH0oAgAAjQcAAA8AAAAAAAAAAAAgAAAAAAAAAGZ1enouc2NyaXB0X2FwaVBLBQYAAAAAAQABAD0AAABVAgAAAAA= ; defstring doesn't have a script_api dependencies#1 = https://github.com/subsoap/defstring/archive/master.zip ; The following deps all have script_api From 00147862e2c054ec0947007c9835c2a1bbe907df Mon Sep 17 00:00:00 2001 From: thinknathan Date: Sun, 4 Feb 2024 00:13:06 -0800 Subject: [PATCH 7/7] More type safety --- src/xtgen.ts | 6 +++--- xtgen.mjs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/xtgen.ts b/src/xtgen.ts index 642cb06..2ca35ad 100644 --- a/src/xtgen.ts +++ b/src/xtgen.ts @@ -304,7 +304,7 @@ function isApiFunc( // Sanitizes name function getName(name: string, isParam: boolean) { - let modifiedName = name; + let modifiedName = String(name); // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { @@ -359,7 +359,7 @@ function getType( } function sanitizeForComment(str: string) { - return str.replace(/\*\//g, ''); + return str.replace(/\*\//g, '*\\/'); } // Transforms and sanitizes descriptions @@ -576,7 +576,7 @@ function generateTypeScriptDefinitions( api.forEach((entry) => { // Handle nested properties - if (entry.name && entry.name.includes('.')) { + if (typeof entry.name === 'string' && entry.name.includes('.')) { const namePieces = entry.name.split('.'); const entryNamespace = namePieces[0]; const entryName = namePieces[1]; diff --git a/xtgen.mjs b/xtgen.mjs index 6800492..fbfd04a 100644 --- a/xtgen.mjs +++ b/xtgen.mjs @@ -268,7 +268,7 @@ function isApiFunc(entry) { } // Sanitizes name function getName(name, isParam) { - let modifiedName = name; + let modifiedName = String(name); // Check against the reserved keywords in TypeScript if (INVALID_NAMES.includes(modifiedName)) { modifiedName = modifiedName + '_'; @@ -312,7 +312,7 @@ function getType(type, context) { return defaultType; } function sanitizeForComment(str) { - return str.replace(/\*\//g, ''); + return str.replace(/\*\//g, '*\\/'); } // Transforms and sanitizes descriptions function getComments(entry) { @@ -487,7 +487,7 @@ function generateTypeScriptDefinitions(api, details) { const namespaces = {}; api.forEach((entry) => { // Handle nested properties - if (entry.name && entry.name.includes('.')) { + if (typeof entry.name === 'string' && entry.name.includes('.')) { const namePieces = entry.name.split('.'); const entryNamespace = namePieces[0]; const entryName = namePieces[1];