From 30798b6e442ef3f4b947a8dbf8c31f365ce5d47e Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Mon, 4 Nov 2024 07:10:03 -0500 Subject: [PATCH] add script to check typescript against jsdoc and map back to coffee --- desired-output.coffee | 108 +++++++++++++++++++++++++++++++++++++++ package-lock.json | 29 +++++++++++ package.json | 1 + tsc-map-check.coffee | 106 ++++++++++++++++++++++++++++++++++++++ typecheck-example.coffee | 8 +++ 5 files changed, 252 insertions(+) create mode 100644 desired-output.coffee create mode 100644 tsc-map-check.coffee create mode 100644 typecheck-example.coffee diff --git a/desired-output.coffee b/desired-output.coffee new file mode 100644 index 0000000000..d67d7868c3 --- /dev/null +++ b/desired-output.coffee @@ -0,0 +1,108 @@ +### +jsdoc: +/** + * @type {(x: number) => number} + * / +var f; + +/** + * @param {number} x + * @returns {number} + * / +f = function(x) { + return x + 3; +} + +.d.ts: +/** + * @type {(x: number) => number} + * / +declare var f: (x: number) => number; +### +# f<[=> number]> = (x<[number]>) -> x + 3 +# f<[(x: number) => number]> = (x) -> x + 3 +f = (x) -> x + 3 + +### +jsdoc: +/** + * @type {{a: number}} + * / +var x; + +x = { + a: 3 +}; + +.d.ts: +/** + * @type {{a: number}} + * / +declare var x: { + a: number; +}; +### +# x = {a<[number]>: 3} +# x<[{a: number}]> = {a: 3} +x = {a: 3} + +### +jsdoc: +/** + * @type {({a}: {a: string}) => string} + * / +var g; + +/** + * @param {{a: string}} _ + * @returns {string} + * / +g = function({a: x}) { + return x; +} + +.d.ts: +/** + * @type {({a}: {a: string}) => string} + * / +declare var g: ({ a }: { + a: string; +}) => string; +### +# g<[=> string]> = ({a<[string]>: x}) -> x +# this one does not rename the field: +# g<[=> string]> = ({a<[string]>}) -> a +g = ({a: x}) -> x + +### +jsdoc: +/** + * @type {({a, b, c, e}: {a: number, b?: number, c: {d?: number}, e: [f: number]}) => number} + * / +var h; + +/** + * @param {{a: number, b?: number, c: {d?: number}, e: [f: number]}} _ + * @returns number + * / +h = function({a, b = 3, c: {d = 3}, e: [f]}) { + return a + b + d; +}; + +.d.ts: +/** + * @type {({a, b, c, e}: {a: number, b?: number, c: {d?: number}, e: [f: number]}) => number} + * / +declare var h: ({ a, b, c, e }: { + a: number; + b?: number; + c: { + d?: number; + }; + e: [f: number]; +}) => number; +### +# h<[=> number]> = ({a<[number]>, b<[?number]> = 3, c: {d<[?number]> = 3}, e: [f<[number]>]}) -> +# a + b + d +h = ({a, b = 3, c: {d = 3}, e: [f]}) -> + a + b + d + f diff --git a/package-lock.json b/package-lock.json index 77920e4a60..f49376a793 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "jison": "~0.4.18", "markdown-it": "~13.0.0", "puppeteer": "~13.6.0", + "typescript": "^5.6.3", "underscore": "~1.13.3", "webpack": "~5.72.0" }, @@ -3929,6 +3930,20 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", @@ -4207,6 +4222,14 @@ "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } } }, "@babel/helper-annotate-as-pure": { @@ -7091,6 +7114,12 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, + "typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true + }, "uc.micro": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", diff --git a/package.json b/package.json index 06348d75b7..33ac359e63 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "jison": "~0.4.18", "markdown-it": "~13.0.0", "puppeteer": "~13.6.0", + "typescript": "^5.6.3", "underscore": "~1.13.3", "webpack": "~5.72.0" } diff --git a/tsc-map-check.coffee b/tsc-map-check.coffee new file mode 100644 index 0000000000..29c404ff15 --- /dev/null +++ b/tsc-map-check.coffee @@ -0,0 +1,106 @@ +assert = require 'assert' +fs = require 'fs' +path = require 'path' + +coffee = require './lib/coffeescript/coffeescript.js' +ts = require 'typescript' + +coffeeCompileWithSourceMap = (fileNames) -> + for f in fileNames + assert f.endsWith '.coffee' + out = f.replace /\.coffee$/, '.js' + mapOut = "#{out}.map" + code = fs.readFileSync f, 'utf8' + {js, v3SourceMap, sourceMap} = coffee.compile code, + filename: f + generatedFile: out + bare: yes + header: no + sourceMap: yes + sourceRoot: process.cwd() + fs.writeFileSync out, "#{js}\n//# sourceMappingURL=#{mapOut}\n" + fs.writeFileSync mapOut, "#{v3SourceMap}\n" + { + original: f + opath: path.resolve f + code: code + codeLines: code.split '\n' + js: out + mapFile: mapOut + map: sourceMap + } + +compiled = coffeeCompileWithSourceMap process.argv[2..] +byJsPath = {} +for {js, original, opath, code, codeLines, map} in compiled + byJsPath[path.resolve js] = {original, opath, code, codeLines, map} + +generateMappedCoffeeSource = (name, filePath, content) -> + { + ...(ts.createSourceFile name, content, ts.ScriptTarget.Latest, no, ts.ScriptKind.Unknown), + statements: [], + parseDiagnostics: [], + identifiers: new Map, + path: filePath, + resolvedPath: filePath, + originalFileName: name + } + +locationToOffset = (contentLines, line, column) -> + offset = 0 + for i in [0...line] + cur = contentLines[i] + # NB: add 1 for newline + offset += cur.length + 1 + offset += column + offset + +mapSpan = (diag, map, contentLines, content) -> + {line: startLine, character: startCol} = ts.getLineAndCharacterOfPosition diag.file, diag.start + {line: endLine, character: endCol} = ts.getLineAndCharacterOfPosition diag.file, (diag.start + diag.length) + + [origStartLine, origStartCol] = map.sourceLocation [startLine, startCol] + [origEndLine, origEndCol] = map.sourceLocation [endLine, endCol] + + lengthInferred = origEndLine == origStartLine and origEndCol == 0 + + origStart = locationToOffset contentLines, origStartLine, origStartCol + + origEnd = if lengthInferred then origStart + diag.length + else locationToOffset contentLines, origEndLine, origEndCol + + {start: origStart, length: origEnd - origStart} + +checkMappedJsDoc = (mappedCompiled, options) -> + fileNames = (k for own k of mappedCompiled) + for f in fileNames + assert f.endsWith '.js' + program = ts.createProgram fileNames, options + emitResult = program.emit() + + rawDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) + + mappedDiagnostics = for diag in rawDiagnostics + if not diag.file? then diag else + {original, opath, code, codeLines, map} = mappedCompiled[diag.file.resolvedPath] + realSource = generateMappedCoffeeSource original, opath, code + {start: realStart, length: realLength} = mapSpan diag, map, codeLines, code + { + ...diag, + file: realSource, + start: realStart, + length: realLength, + } + + console.log ts.formatDiagnosticsWithColorAndContext mappedDiagnostics, + getCurrentDirectory: -> process.cwd() + getNewLine: -> '\n' + getCanonicalFileName: (fileName) -> fileName + + exitCode = if emitResult.emitSkipped then 1 else 0 + process.exit exitCode + +checkMappedJsDoc byJsPath, + allowJs: yes + checkJs: yes + noEmit: yes diff --git a/typecheck-example.coffee b/typecheck-example.coffee new file mode 100644 index 0000000000..969e67e2a1 --- /dev/null +++ b/typecheck-example.coffee @@ -0,0 +1,8 @@ +###* + * @type {(x: number) => number} +### +f = (x) -> x + +f("asdf") + +x = 3