Skip to content

Commit

Permalink
add script to check typescript against jsdoc and map back to coffee
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Nov 4, 2024
1 parent 817c39a commit 30798b6
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 0 deletions.
108 changes: 108 additions & 0 deletions desired-output.coffee
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
106 changes: 106 additions & 0 deletions tsc-map-check.coffee
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions typecheck-example.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
###*
* @type {(x: number) => number}
###
f = (x) -> x

f("asdf")

x = 3

0 comments on commit 30798b6

Please sign in to comment.