Use babel instead of ts lib and do only doctests
This commit is contained in:
parent
587ac52c05
commit
31921f95bf
11 changed files with 3788 additions and 216 deletions
27
package.json
27
package.json
|
@ -7,8 +7,11 @@
|
||||||
"typescript-doctest": "src/main.js"
|
"typescript-doctest": "src/main.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "tsc",
|
"build": "tsc",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test:watch": "tsc --watch & ava --watch dist/test",
|
||||||
|
"test": "tsc && ava dist/test",
|
||||||
|
"coverage": "tsc && nyc ava dist/test",
|
||||||
|
"prettier": "prettier --list-different --write src/*ts* test/*ts*"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -26,9 +29,25 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/danr/typescript-doctest#readme",
|
"homepage": "https://github.com/danr/typescript-doctest#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"typescript": "^2.6.1"
|
"babel-generator": "^6.26.1",
|
||||||
|
"babel-types": "^6.26.0",
|
||||||
|
"babylon": "^6.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^8.0.47"
|
"@types/babel-generator": "^6.25.1",
|
||||||
|
"@types/babel-types": "^7.0.0",
|
||||||
|
"@types/babylon": "^6.16.2",
|
||||||
|
"@types/node": "^9.4.5",
|
||||||
|
"ava": "^0.25.0",
|
||||||
|
"prettier": "^1.10.2",
|
||||||
|
"typescript": "^2.7.1"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"jsxBracketSameLine": true,
|
||||||
|
"bracketSpacing": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
343
src/main.ts
343
src/main.ts
|
@ -1,168 +1,154 @@
|
||||||
#!/usr/bin/env node
|
import * as babylon from 'babylon'
|
||||||
import * as ts from 'typescript'
|
import * as babel from 'babel-types'
|
||||||
|
import generate from 'babel-generator'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
|
||||||
const is_doctest = (s: string) => s.match( /\/\/[ \t]*=>/) != null
|
import * as util from 'util'
|
||||||
const doctest_rhs = (s: string) => s.match(/^\s*\/\/[ \t]*=>([^\n]*)/m)
|
|
||||||
|
|
||||||
function replicate<A>(i: number, x: A): A[] {
|
util.inspect.defaultOptions.depth = 5
|
||||||
const out = [] as A[]
|
util.inspect.defaultOptions.colors = true
|
||||||
while (i-- > 0) out.push(x);
|
const pp = (x: any) => (console.dir(x), console.log())
|
||||||
|
|
||||||
|
const opts: babylon.BabylonOptions = {
|
||||||
|
plugins: [
|
||||||
|
'estree',
|
||||||
|
'jsx',
|
||||||
|
'flow',
|
||||||
|
'classConstructorCall',
|
||||||
|
'doExpressions',
|
||||||
|
'objectRestSpread',
|
||||||
|
'decorators',
|
||||||
|
'classProperties',
|
||||||
|
'exportExtensions',
|
||||||
|
'asyncGenerators',
|
||||||
|
'functionBind',
|
||||||
|
'functionSent',
|
||||||
|
'dynamicImport',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_doctest = (s: string) => s.match(/\/\/[ \t]*=>/) != null
|
||||||
|
const doctest_rhs = (s: string) => s.match(/^\s*[ \t]*=>((.|\n)*)$/m)
|
||||||
|
|
||||||
|
interface Equality {
|
||||||
|
tag: '=='
|
||||||
|
lhs: string
|
||||||
|
rhs: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Statement {
|
||||||
|
tag: 'Statement'
|
||||||
|
stmt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script = (Statement | Equality)[]
|
||||||
|
|
||||||
|
export function test(s: string): Script {
|
||||||
|
const lin = (ast: babel.Node) => generate(ast, {comments: false, compact: true}).code
|
||||||
|
const ast = babylon.parse(s, opts)
|
||||||
|
return ast.program.body.map((stmt): Statement | Equality => {
|
||||||
|
const comment = (stmt.trailingComments || [{value: ''}])[0].value
|
||||||
|
const rhs = doctest_rhs(comment)
|
||||||
|
if (babel.isExpressionStatement(stmt) && rhs) {
|
||||||
|
const rhs = babylon.parseExpression(comment.replace(/^\s*=>/, ''))
|
||||||
|
return {
|
||||||
|
tag: '==',
|
||||||
|
lhs: lin(stmt.expression),
|
||||||
|
rhs: lin(rhs),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {tag: 'Statement', stmt: lin(stmt)}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tests(docstring: string): Script[] {
|
||||||
|
const out = [] as Script[]
|
||||||
|
docstring.split(/\n\n+/m).forEach(s => {
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
out.push(test(s))
|
||||||
|
}
|
||||||
|
})
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatMap<A, B>(xs: A[], f: (a: A) => B[]): B[] {
|
export interface Comment {
|
||||||
return ([] as B[]).concat(...xs.map(f))
|
comment: string
|
||||||
|
context: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
function flatten<A>(xss: A[][]): A[] {
|
export function Comments(s: string): Comment[] {
|
||||||
return ([] as A[]).concat(...xss)
|
const out: Comment[] = []
|
||||||
}
|
function add_comment(c: babel.Comment, context: string | null) {
|
||||||
|
out.push({comment: c.value, context})
|
||||||
function silently<A>(m: () => A): A | undefined {
|
|
||||||
try {
|
|
||||||
return m()
|
|
||||||
} catch (e) {
|
|
||||||
// console.error(e)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Top = {filename: string, defs: Defs}[]
|
|
||||||
|
|
||||||
type Defs = Def[]
|
|
||||||
|
|
||||||
interface Def {
|
|
||||||
name: string,
|
|
||||||
type?: string,
|
|
||||||
doc: string,
|
|
||||||
exported: boolean,
|
|
||||||
typedef: boolean,
|
|
||||||
flags: string[],
|
|
||||||
children: Defs,
|
|
||||||
kind: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
function walk<A>(defs: Defs, f: (def: Def, d: number) => A[], d0: number = 0): A[] {
|
|
||||||
return flatten(defs.map(d => f(d, d0).concat(...walk(d.children, f, d0 + 1))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generate documentation for all classes in a set of .ts files
|
|
||||||
|
|
||||||
Adapted from TS wiki about using the compiler API */
|
|
||||||
function generateDocumentation(program: ts.Program, filenames: string[]): Top {
|
|
||||||
const checker = program.getTypeChecker()
|
|
||||||
const printer = ts.createPrinter()
|
|
||||||
return program.getSourceFiles()
|
|
||||||
.filter(file => -1 != filenames.indexOf(file.fileName))
|
|
||||||
.map(file => ({
|
|
||||||
filename: file.fileName,
|
|
||||||
defs: flatten(file.statements.map(visits))
|
|
||||||
}))
|
|
||||||
|
|
||||||
function isNodeExported(node: ts.Node): boolean {
|
|
||||||
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function visits(node: ts.Node): Defs {
|
const ast = babylon.parse(s, opts)
|
||||||
if (ts.isVariableStatement(node)) {
|
|
||||||
// top-level const
|
traverse(ast, node => {
|
||||||
return flatten(node.declarationList.declarations.map(visits))
|
let context: null | string = null
|
||||||
// } else if (ts.isExportAssignment(node)) {
|
function has_key(x: any): x is {key: babel.Identifier} {
|
||||||
// return { exAss: ts.forEachChild(node, visit) }
|
return isObject(x) && 'key' in x && babel.isIdentifier((x as any).key)
|
||||||
// } else if (ts.isExportDeclaration(node)) {
|
}
|
||||||
// return { exDecl: ts.forEachChild(node, visit) }
|
function has_id(x: any): x is {id: babel.Identifier} {
|
||||||
} if (
|
return isObject(x) && 'id' in x && babel.isIdentifier((x as any).id)
|
||||||
( ts.isInterfaceDeclaration(node)
|
}
|
||||||
|| ts.isClassDeclaration(node)
|
if (babel.isVariableDeclaration(node)) {
|
||||||
|| ts.isFunctionDeclaration(node)
|
const ds = node.declarations
|
||||||
|| ts.isMethodDeclaration(node)
|
if (ds.length == 1) {
|
||||||
|| ts.isPropertyDeclaration(node) // fields in classes
|
const d = ds[0]
|
||||||
|| ts.isTypeElement(node) // fields in interface records
|
if (has_id(d)) {
|
||||||
|| ts.isTypeAliasDeclaration(node) // type A = ...
|
context = d.id.name
|
||||||
|| ts.isVariableDeclaration(node) // top-level const
|
|
||||||
|| ts.isModuleDeclaration(node)
|
|
||||||
) && node.name) {
|
|
||||||
const symbol = checker.getSymbolAtLocation(node.name);
|
|
||||||
const doc = (((node as any).jsDoc || [])[0] || {}).comment || ''
|
|
||||||
if (symbol) {
|
|
||||||
const out: Def = {
|
|
||||||
name: symbol.name,
|
|
||||||
doc,
|
|
||||||
exported: isNodeExported(node),
|
|
||||||
kind: ts.SyntaxKind[node.kind],
|
|
||||||
flags: [
|
|
||||||
ts.ModifierFlags.None,
|
|
||||||
ts.ModifierFlags.Export,
|
|
||||||
ts.ModifierFlags.Ambient,
|
|
||||||
ts.ModifierFlags.Public,
|
|
||||||
ts.ModifierFlags.Private,
|
|
||||||
ts.ModifierFlags.Protected,
|
|
||||||
ts.ModifierFlags.Static,
|
|
||||||
ts.ModifierFlags.Readonly,
|
|
||||||
ts.ModifierFlags.Abstract,
|
|
||||||
ts.ModifierFlags.Async,
|
|
||||||
ts.ModifierFlags.Default,
|
|
||||||
ts.ModifierFlags.Const
|
|
||||||
].map(flag => ts.ModifierFlags[symbol.flags & flag])
|
|
||||||
.filter(flag => flag != 'None'),
|
|
||||||
typedef: ts.isInterfaceDeclaration(node)
|
|
||||||
|| ts.isClassDeclaration(node)
|
|
||||||
|| ts.isTypeAliasDeclaration(node),
|
|
||||||
type:
|
|
||||||
silently(() =>
|
|
||||||
(symbol.valueDeclaration && !ts.isClassDeclaration(node))
|
|
||||||
? checker.typeToString(checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration), node, 65535)
|
|
||||||
: undefined
|
|
||||||
),
|
|
||||||
children: []
|
|
||||||
}
|
}
|
||||||
if ( ts.isInterfaceDeclaration(node) ) {
|
|
||||||
out.children = flatten(node.members.map(visits))
|
|
||||||
}
|
|
||||||
if ( ts.isClassDeclaration(node) ) {
|
|
||||||
out.children = flatten(node.members.map(visits))
|
|
||||||
}
|
|
||||||
if ( ts.isModuleDeclaration(node) && node.body ) {
|
|
||||||
const b = node.body
|
|
||||||
if (b.kind == ts.SyntaxKind.ModuleBlock) {
|
|
||||||
out.children = flatten(b.statements.map(visits))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [out]
|
|
||||||
}
|
}
|
||||||
return []
|
} else if (has_id(node)) {
|
||||||
} else if (ts.isModuleBlock(node)) {
|
context = node.id.name
|
||||||
return flatten(node.getChildren().map(visits))
|
} else if (has_key(node)) {
|
||||||
} else {
|
context = node.key.name
|
||||||
console.error("Ignoring " + ts.SyntaxKind[node.kind])
|
}
|
||||||
return []
|
if (isObject(node)) {
|
||||||
|
function add_comments(s: string) {
|
||||||
|
if (s in node && Array.isArray(node[s])) {
|
||||||
|
;(node[s] as any[]).forEach(c => {
|
||||||
|
if ('type' in c) {
|
||||||
|
if (c.type == 'CommentBlock' || c.type == 'CommentLine') {
|
||||||
|
add_comment(c, context)
|
||||||
|
if (context == null) {
|
||||||
|
// pp({c, node})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isObject(node)) {
|
||||||
|
add_comments('leadingComments')
|
||||||
|
add_comments('innerComments')
|
||||||
|
// add_comments('trailingComments')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(x: any): x is object {
|
||||||
|
return x !== null && typeof x === 'object' && !Array.isArray(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverse(x: any, f: (x: any) => void): void {
|
||||||
|
f(x)
|
||||||
|
if (Array.isArray(x)) {
|
||||||
|
x.map(y => traverse(y, f))
|
||||||
|
}
|
||||||
|
if (isObject(x)) {
|
||||||
|
for (const k in x) {
|
||||||
|
traverse((x as any)[k], f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function script(filename: string, s: string): string[] {
|
/*
|
||||||
const pwoc = ts.createPrinter({removeComments: true})
|
|
||||||
const f = ts.createSourceFile('_doctest_' + filename, s, ts.ScriptTarget.ES5, true, ts.ScriptKind.TS)
|
|
||||||
const out =
|
|
||||||
f.statements.map(
|
|
||||||
(now, i) => {
|
|
||||||
if (ts.isExpressionStatement(now)) {
|
|
||||||
const next = f.statements[i+1] // zip with next
|
|
||||||
const [a, z] = next ? [next.pos, next.end] : [now.end, f.end]
|
|
||||||
const after = f.text.slice(a, z)
|
|
||||||
const m = doctest_rhs(after)
|
|
||||||
if (m && m[1]) {
|
|
||||||
const lhs = pwoc.printNode(ts.EmitHint.Expression, now.expression, f)
|
|
||||||
const rhs = m[1].trim()
|
|
||||||
return 'assert.deepEqual(' + lhs + ', ' + rhs + ', ' + JSON.stringify(rhs) + ')'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pwoc.printNode(ts.EmitHint.Unspecified, now, f)
|
|
||||||
})
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
function test_script_one(filename: string, d: Def): string[] {
|
function test_script_one(filename: string, d: Def): string[] {
|
||||||
const out = [] as string[]
|
const out = [] as string[]
|
||||||
let tests = 0
|
let tests = 0
|
||||||
|
@ -170,9 +156,8 @@ function test_script_one(filename: string, d: Def): string[] {
|
||||||
if (is_doctest(s)) {
|
if (is_doctest(s)) {
|
||||||
// todo: typecheck s now
|
// todo: typecheck s now
|
||||||
out.push(
|
out.push(
|
||||||
'test(' + JSON.stringify(d.name + ' ' + ++tests) + ', assert => {',
|
'test(' + JSON.stringify(d.name + ' ' + ++tests) + ', t => {',
|
||||||
...script(filename, s).map(l => ' ' + l),
|
...script(filename, s).map(l => ' ' + l),
|
||||||
' assert.end()',
|
|
||||||
'})',
|
'})',
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
@ -182,63 +167,11 @@ function test_script_one(filename: string, d: Def): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_script(top: Top) {
|
function test_script(top: Top) {
|
||||||
return ["import * as test from 'tape'"].concat(...top.map(
|
return ["import {test} from 'ava'"].concat(...top.map(
|
||||||
({filename, defs}) => walk(defs, (d) => test_script_one(filename, d)))
|
({filename, defs}) => walk(defs, (d) => test_script_one(filename, d)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function prettyKind(kind: string) {
|
|
||||||
return kind.replace('Declaration', '').toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
function toc_one(def: Def, i: number): string[] {
|
|
||||||
if (def.exported || i > 0) {
|
|
||||||
return [
|
|
||||||
replicate(i, ' ').join('') +
|
|
||||||
'* ' +
|
|
||||||
(def.children.length == 0 ? '' : (prettyKind(def.kind) + ' ')) +
|
|
||||||
def.name
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toc(top: Top): string[] {
|
|
||||||
return flatten(top.map(({defs}) => walk(defs, toc_one)))
|
|
||||||
}
|
|
||||||
|
|
||||||
function doc_one(def: Def, i: number): string[] {
|
|
||||||
const out = [] as string[]
|
|
||||||
if (def.exported || i > 0) {
|
|
||||||
let indent = ''
|
|
||||||
if (def.children.length == 0) {
|
|
||||||
//const method = (def.kind == 'MethodDeclaration') ? 'method ' : ''
|
|
||||||
out.push('* ' + '**' + def.name + '**: `' + def.type + '`')
|
|
||||||
indent = ' '
|
|
||||||
} else {
|
|
||||||
out.push('### ' + prettyKind(def.kind) + ' ' + def.name)
|
|
||||||
}
|
|
||||||
def.doc.split(/\n\n+/).forEach(s => {
|
|
||||||
out.push('')
|
|
||||||
if (is_doctest(s)) {
|
|
||||||
out.push(indent + '```typescript')
|
|
||||||
}
|
|
||||||
const lines = s.split('\n')
|
|
||||||
lines.forEach(line => out.push(indent + line))
|
|
||||||
if (is_doctest(s)) {
|
|
||||||
out.push(indent + '```')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
function doc(top: Top) {
|
|
||||||
return flatten(top.map(({defs}) => walk(defs, doc_one)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const filenames = [] as string[]
|
const filenames = [] as string[]
|
||||||
const argv = process.argv.slice(2)
|
const argv = process.argv.slice(2)
|
||||||
const outputs = [] as ((top: Top) => string[])[]
|
const outputs = [] as ((top: Top) => string[])[]
|
||||||
|
@ -279,6 +212,7 @@ const outputs = [] as ((top: Top) => string[])[]
|
||||||
|
|
||||||
typescript-doctests src/*.ts -i Header.md --toc --doc -i Footer.md > README.md
|
typescript-doctests src/*.ts -i Header.md --toc --doc -i Footer.md > README.md
|
||||||
`)
|
`)
|
||||||
|
process.exit(1)
|
||||||
} else {
|
} else {
|
||||||
program = ts.createProgram(filenames, {
|
program = ts.createProgram(filenames, {
|
||||||
target: ts.ScriptTarget.ES5,
|
target: ts.ScriptTarget.ES5,
|
||||||
|
@ -289,3 +223,4 @@ const outputs = [] as ((top: Top) => string[])[]
|
||||||
outputs.forEach(m => m(top).forEach(line => console.log(line)))
|
outputs.forEach(m => m(top).forEach(line => console.log(line)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
146
test/test.ts
Normal file
146
test/test.ts
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
import * as main from '../src/main'
|
||||||
|
import {test} from 'ava'
|
||||||
|
|
||||||
|
const c = (comment: string, context: string | null) => ({comment, context})
|
||||||
|
|
||||||
|
test('tests', t => {
|
||||||
|
t.deepEqual(
|
||||||
|
main.tests(`*
|
||||||
|
|
||||||
|
foo // => 1
|
||||||
|
|
||||||
|
`),
|
||||||
|
[[{tag: '==', lhs: `foo`, rhs: `1`}]]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tests', t => {
|
||||||
|
t.deepEqual(
|
||||||
|
main.tests(`*
|
||||||
|
|
||||||
|
a
|
||||||
|
b /* => 1 +
|
||||||
|
2 +
|
||||||
|
3
|
||||||
|
*/
|
||||||
|
c // => 1
|
||||||
|
d
|
||||||
|
|
||||||
|
`),
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{tag: 'Statement', stmt: 'a;'},
|
||||||
|
{tag: '==', lhs: 'b', rhs: '1+2+3'},
|
||||||
|
{tag: '==', lhs: 'c', rhs: '1'},
|
||||||
|
{tag: 'Statement', stmt: 'd;'},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test.failing('modules and namespace', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** ns */
|
||||||
|
namespace ns {}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* m ', 'm'), c('* ns ', 'ns')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('const', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** u */
|
||||||
|
const u = 1
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* u ', 'u')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('const object', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** k */
|
||||||
|
const k = {
|
||||||
|
/** a */
|
||||||
|
a: 1,
|
||||||
|
/** b */
|
||||||
|
b(x: string) { return x+x }
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* k ', 'k'), c('* a ', 'a'), c('* b ', 'b')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('object deconstruction', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** hello */
|
||||||
|
const {u, v} = {u: 1, v: 2}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* hello ', null)])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('function', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** v */
|
||||||
|
function v(s: string): number {
|
||||||
|
return s.length + 1
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* v ', 'v')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('class', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** C */
|
||||||
|
class C<A> {
|
||||||
|
/** constructor */
|
||||||
|
constructor() {}
|
||||||
|
/** m */
|
||||||
|
m(s: Array<number>): Array<string> {
|
||||||
|
}
|
||||||
|
/** p */
|
||||||
|
p: Array<number>
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [
|
||||||
|
c('* C ', 'C'),
|
||||||
|
c('* constructor ', 'constructor'),
|
||||||
|
c('* m ', 'm'),
|
||||||
|
c('* p ', 'p'),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('interface', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** I */
|
||||||
|
interface I<A> {
|
||||||
|
/** i */
|
||||||
|
i: A,
|
||||||
|
/** j */
|
||||||
|
j(a: A): string
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* I ', 'I'), c('* i ', 'i'), c('* j ', 'j')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('type', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
/** T */
|
||||||
|
type T = number
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* T ', 'T')])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('anywhere', t => {
|
||||||
|
const cs = main.Comments(`
|
||||||
|
const $ = () => {
|
||||||
|
/** test1 */
|
||||||
|
const w = 1
|
||||||
|
|
||||||
|
/** test2 */
|
||||||
|
function f(x) {
|
||||||
|
return x * x
|
||||||
|
}
|
||||||
|
|
||||||
|
/** test3 */
|
||||||
|
return f(f(w))
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
t.deepEqual(cs, [c('* test1 ', 'w'), c('* test2 ', 'f'), c('* test3 ', null)])
|
||||||
|
})
|
|
@ -1,10 +1,22 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"outDir": "./dist",
|
||||||
"module": "commonjs",
|
"sourceMap": true,
|
||||||
"strict": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitAny": true,
|
"strict": true,
|
||||||
"strictNullChecks": true,
|
"target": "es6",
|
||||||
"alwaysStrict": true
|
"module": "commonjs",
|
||||||
}
|
"moduleResolution": "node",
|
||||||
|
"jsx": "react",
|
||||||
|
"lib": ["es2017", "dom"],
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"allowUnreachableCode": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"parcelTsPluginOptions": {
|
||||||
|
"transpileOnly": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
7
unused-test-files/c.ts
Normal file
7
unused-test-files/c.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class X {
|
||||||
|
J: {
|
||||||
|
u: number
|
||||||
|
} = {
|
||||||
|
u: 1
|
||||||
|
}
|
||||||
|
}
|
11
unused-test-files/ex.ts
Normal file
11
unused-test-files/ex.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export const j = 1
|
||||||
|
export module A {
|
||||||
|
export const c = 1
|
||||||
|
export const s = 1
|
||||||
|
export interface J {
|
||||||
|
u: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export namespace N {
|
||||||
|
export const d = 1
|
||||||
|
}
|
89
unused-test-files/example.ts
Normal file
89
unused-test-files/example.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
/** I */
|
||||||
|
export interface I {
|
||||||
|
/** k */
|
||||||
|
k: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** H */
|
||||||
|
interface H {
|
||||||
|
/** k */
|
||||||
|
k: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** C */
|
||||||
|
export class C {
|
||||||
|
/** f */
|
||||||
|
f = 1
|
||||||
|
/** g */
|
||||||
|
g: number
|
||||||
|
/** new */
|
||||||
|
constructor(x: number) { this.g = x }
|
||||||
|
/** s */
|
||||||
|
static s(y: number): I { return {k: y} }
|
||||||
|
/** m */
|
||||||
|
m(z: number): I { return {k: z} }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** T */
|
||||||
|
type T = C
|
||||||
|
|
||||||
|
/** c */
|
||||||
|
const c = new C(1)
|
||||||
|
|
||||||
|
/** M */
|
||||||
|
export module M {
|
||||||
|
/** MI */
|
||||||
|
export interface MI {
|
||||||
|
/** M k */
|
||||||
|
k: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** MC */
|
||||||
|
export class MC {
|
||||||
|
/** M f */
|
||||||
|
f = 1
|
||||||
|
/** M g */
|
||||||
|
g: number
|
||||||
|
/** M new */
|
||||||
|
constructor(x: number) { this.g = x }
|
||||||
|
/** M s */
|
||||||
|
static s(y: number): MI { return {k: y} }
|
||||||
|
/** M m */
|
||||||
|
m(z: number): MI { return {k: z} }
|
||||||
|
|
||||||
|
/** M p */
|
||||||
|
private p(z: number): MI { return {k: z} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type MT = MC
|
||||||
|
|
||||||
|
export const c = new MC(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HM */
|
||||||
|
module HM {
|
||||||
|
/** HMI */
|
||||||
|
interface HMI {
|
||||||
|
/** HM k */
|
||||||
|
k: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HMC */
|
||||||
|
class HMC {
|
||||||
|
/** HM f */
|
||||||
|
f = 1
|
||||||
|
/** HM g */
|
||||||
|
g: number
|
||||||
|
/** HM new */
|
||||||
|
constructor(x: number) { this.g = x }
|
||||||
|
/** HM s */
|
||||||
|
static s(y: number): HMI { return {k: y} }
|
||||||
|
/** HM m */
|
||||||
|
m(z: number): HMI { return {k: z} }
|
||||||
|
}
|
||||||
|
|
||||||
|
type HMT = HMC
|
||||||
|
|
||||||
|
const c = new HMC(1)
|
||||||
|
}
|
23
unused-test-files/i.ts
Normal file
23
unused-test-files/i.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/** The awesome iface
|
||||||
|
|
||||||
|
1 // => 1
|
||||||
|
|
||||||
|
This is not indentend
|
||||||
|
*/
|
||||||
|
export interface B {
|
||||||
|
b: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class C {
|
||||||
|
/**
|
||||||
|
|
||||||
|
1 // => 1
|
||||||
|
much.to.test()
|
||||||
|
yes()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
more stuff*/
|
||||||
|
static boo() { return 1 }
|
||||||
|
}
|
||||||
|
|
417
unused-test-files/main2.ts
Normal file
417
unused-test-files/main2.ts
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
import * as babylon from 'babylon'
|
||||||
|
import * as babel from 'babel-types'
|
||||||
|
import generate from 'babel-generator'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
import * as util from 'util'
|
||||||
|
|
||||||
|
util.inspect.defaultOptions.depth = 5
|
||||||
|
util.inspect.defaultOptions.colors = true
|
||||||
|
const pp = (x: any) => (console.dir(x), console.log())
|
||||||
|
|
||||||
|
const opts: babylon.BabylonOptions = {plugins: [
|
||||||
|
'estree' ,
|
||||||
|
'jsx' ,
|
||||||
|
'flow' ,
|
||||||
|
'classConstructorCall' ,
|
||||||
|
'doExpressions' ,
|
||||||
|
'objectRestSpread' ,
|
||||||
|
'decorators' ,
|
||||||
|
'classProperties' ,
|
||||||
|
'exportExtensions' ,
|
||||||
|
'asyncGenerators' ,
|
||||||
|
'functionBind' ,
|
||||||
|
'functionSent' ,
|
||||||
|
'dynamicImport']}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const is_doctest = (s: string) => s.match(/\/\/[ \t]*=>/) != null
|
||||||
|
const doctest_rhs = (s: string) => s.match(/^\s*[ \t]*=>((.|\n)*)$/m)
|
||||||
|
|
||||||
|
interface Equality {
|
||||||
|
tag: '==',
|
||||||
|
lhs: string,
|
||||||
|
rhs: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Statement {
|
||||||
|
tag: 'Statement'
|
||||||
|
stmt: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script = (Statement | Equality)[]
|
||||||
|
|
||||||
|
export function test(s: string): Script {
|
||||||
|
const lin = (ast: babel.Node) => generate(ast ,{comments: false, compact: true}).code
|
||||||
|
const ast = babylon.parse(s, opts)
|
||||||
|
return ast.program.body.map((stmt): Statement | Equality => {
|
||||||
|
const comment = (stmt.trailingComments || [{value: ''}])[0].value
|
||||||
|
const rhs = doctest_rhs(comment)
|
||||||
|
if (babel.isExpressionStatement(stmt) && rhs) {
|
||||||
|
const rhs = babylon.parseExpression(comment.replace(/^\s*=>/, ''))
|
||||||
|
return {
|
||||||
|
tag: '==',
|
||||||
|
lhs: lin(stmt.expression),
|
||||||
|
rhs: lin(rhs),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {tag: 'Statement', stmt: lin(stmt)}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tests(docstring: string): Script[] {
|
||||||
|
const out = [] as Script[]
|
||||||
|
docstring.split(/\n\n+/m).forEach(s => {
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
out.push(test(s))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Comment {
|
||||||
|
comment: string,
|
||||||
|
context: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Comments(s: string): Comment[] {
|
||||||
|
const out: Comment[] = []
|
||||||
|
function add_comment(c: babel.Comment, context: string | null) {
|
||||||
|
out.push({comment: c.value, context})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = babylon.parse(s, opts)
|
||||||
|
|
||||||
|
traverse(ast, node => {
|
||||||
|
let context: null | string = null
|
||||||
|
/*
|
||||||
|
if (babel.isDeclaration(node)) {
|
||||||
|
util.inspect.defaultOptions.depth = 1
|
||||||
|
pp({declaration: node})
|
||||||
|
}
|
||||||
|
if (babel.isMethod(node)) {
|
||||||
|
util.inspect.defaultOptions.depth = 1
|
||||||
|
pp({method: node})
|
||||||
|
}
|
||||||
|
if (isObject(node) && 'type' in node && node.type == 'MethodDefinition') {
|
||||||
|
util.inspect.defaultOptions.depth = 2
|
||||||
|
pp({methodDefn: node})
|
||||||
|
}
|
||||||
|
if (isObject(node) && 'type' in node && node.type == 'ObjectProperty') {
|
||||||
|
util.inspect.defaultOptions.depth = 2
|
||||||
|
pp({objProp: node})
|
||||||
|
}
|
||||||
|
if (isObject(node) && 'type' in node && node.type == 'ObjectMethod') {
|
||||||
|
util.inspect.defaultOptions.depth = 2
|
||||||
|
pp({objMethod: node})
|
||||||
|
}
|
||||||
|
if (isObject(node) && 'type' in node && node.type == 'ObjectExpression') {
|
||||||
|
util.inspect.defaultOptions.depth = 5
|
||||||
|
pp({objExpr: node})
|
||||||
|
}
|
||||||
|
if (isObject(node) && 'type' in node && node.type == 'Property') {
|
||||||
|
util.inspect.defaultOptions.depth = 5
|
||||||
|
pp({property: node})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// context = node as any
|
||||||
|
function has_key(x: any): x is {key: babel.Identifier} {
|
||||||
|
return isObject(x) && 'key' in x && babel.isIdentifier((x as any).key)
|
||||||
|
}
|
||||||
|
function has_id(x: any): x is {id: babel.Identifier} {
|
||||||
|
return isObject(x) && 'id' in x && babel.isIdentifier((x as any).id)
|
||||||
|
}
|
||||||
|
if (babel.isVariableDeclaration(node)) {
|
||||||
|
const ds = node.declarations
|
||||||
|
if (ds.length == 1) {
|
||||||
|
const d = ds[0]
|
||||||
|
if (has_id(d)) {
|
||||||
|
context = d.id.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (has_id(node)) {
|
||||||
|
context = node.id.name
|
||||||
|
} else if (has_key(node)) {
|
||||||
|
context = node.key.name
|
||||||
|
}
|
||||||
|
if (isObject(node)) {
|
||||||
|
function add_comments(s: string) {
|
||||||
|
if (s in node && Array.isArray(node[s])) {
|
||||||
|
(node[s] as any[]).forEach(c => {
|
||||||
|
if ('type' in c) {
|
||||||
|
if (c.type == 'CommentBlock' || c.type == 'CommentLine') {
|
||||||
|
add_comment(c, context)
|
||||||
|
if (context == null) {
|
||||||
|
// pp({c, node})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isObject(node)) {
|
||||||
|
add_comments('leadingComments')
|
||||||
|
add_comments('innerComments')
|
||||||
|
// add_comments('trailingComments')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObject(x: any): x is object {
|
||||||
|
return x !== null && typeof x === 'object' && !Array.isArray(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
function traverse(x: any, f: (x: any) => void): void {
|
||||||
|
f(x)
|
||||||
|
if (Array.isArray(x)) {
|
||||||
|
x.map(y => traverse(y, f))
|
||||||
|
}
|
||||||
|
if (isObject(x)) {
|
||||||
|
for (const k in x) {
|
||||||
|
traverse((x as any)[k], f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
false && pp(Comments(`/** test */ function f(x: string): number { return 1 }
|
||||||
|
|
||||||
|
class Apa {
|
||||||
|
/** something something */
|
||||||
|
|
||||||
|
/** attached to nothing! */
|
||||||
|
|
||||||
|
/** returns important stuff
|
||||||
|
|
||||||
|
j(5) // => 6
|
||||||
|
|
||||||
|
j(9) // => 10
|
||||||
|
*/
|
||||||
|
j(x: number) {
|
||||||
|
/** important stuff */
|
||||||
|
return x + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface B {
|
||||||
|
/** x docstring */
|
||||||
|
x: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** u docstring */
|
||||||
|
const u = {
|
||||||
|
/** ux docstring */
|
||||||
|
ux: 1
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
function script(filename: string, s: string): string[] {
|
||||||
|
return []
|
||||||
|
/*
|
||||||
|
const pwoc = ts.createPrinter({removeComments: true})
|
||||||
|
const f = ts.createSourceFile('_doctest_' + filename, s, ts.ScriptTarget.ES5, true, ts.ScriptKind.TS)
|
||||||
|
const out =
|
||||||
|
f.statements.map(
|
||||||
|
(now, i) => {
|
||||||
|
if (ts.isExpressionStatement(now)) {
|
||||||
|
const next = f.statements[i+1] // zip with next
|
||||||
|
const [a, z] = next ? [next.pos, next.end] : [now.end, f.end]
|
||||||
|
const after = f.text.slice(a, z)
|
||||||
|
const m = doctest_rhs(after)
|
||||||
|
if (m && m[1]) {
|
||||||
|
const lhs = pwoc.printNode(ts.EmitHint.Expression, now.expression, f)
|
||||||
|
const rhs = m[1].trim()
|
||||||
|
return 't.deepEqual(' + lhs + ', ' + rhs + ', ' + JSON.stringify(rhs) + ')'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pwoc.printNode(ts.EmitHint.Unspecified, now, f)
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
const filename = 'unk.ts'
|
||||||
|
|
||||||
|
r.comments.map(d => {
|
||||||
|
d.value.split(/\n\n+/m).map(s => {
|
||||||
|
let tests = 0
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
// todo: typecheck s now
|
||||||
|
const name = 'unk'
|
||||||
|
console.log(
|
||||||
|
'test(' + JSON.stringify(name + ' ' + ++tests) + ', t => {',
|
||||||
|
...script(filename, s).map(l => ' ' + l),
|
||||||
|
'})',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
pp(r.program)
|
||||||
|
pp(r.comments)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
function script(filename: string, s: string): string[] {
|
||||||
|
const pwoc = ts.createPrinter({removeComments: true})
|
||||||
|
const f = ts.createSourceFile('_doctest_' + filename, s, ts.ScriptTarget.ES5, true, ts.ScriptKind.TS)
|
||||||
|
const out =
|
||||||
|
f.statements.map(
|
||||||
|
(now, i) => {
|
||||||
|
if (ts.isExpressionStatement(now)) {
|
||||||
|
const next = f.statements[i+1] // zip with next
|
||||||
|
const [a, z] = next ? [next.pos, next.end] : [now.end, f.end]
|
||||||
|
const after = f.text.slice(a, z)
|
||||||
|
const m = doctest_rhs(after)
|
||||||
|
if (m && m[1]) {
|
||||||
|
const lhs = pwoc.printNode(ts.EmitHint.Expression, now.expression, f)
|
||||||
|
const rhs = m[1].trim()
|
||||||
|
return 't.deepEqual(' + lhs + ', ' + rhs + ', ' + JSON.stringify(rhs) + ')'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pwoc.printNode(ts.EmitHint.Unspecified, now, f)
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_script_one(filename: string, d: Def): string[] {
|
||||||
|
const out = [] as string[]
|
||||||
|
let tests = 0
|
||||||
|
d.doc.split(/\n\n+/m).map(s => {
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
// todo: typecheck s now
|
||||||
|
out.push(
|
||||||
|
'test(' + JSON.stringify(d.name + ' ' + ++tests) + ', t => {',
|
||||||
|
...script(filename, s).map(l => ' ' + l),
|
||||||
|
'})',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_script(top: Top) {
|
||||||
|
return ["import {test} from 'ava'"].concat(...top.map(
|
||||||
|
({filename, defs}) => walk(defs, (d) => test_script_one(filename, d)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function prettyKind(kind: string) {
|
||||||
|
return kind.replace('Declaration', '').toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toc_one(def: Def, i: number): string[] {
|
||||||
|
if (def.exported || i > 0) {
|
||||||
|
return [
|
||||||
|
replicate(i, ' ').join('') +
|
||||||
|
'* ' +
|
||||||
|
(def.children.length == 0 ? '' : (prettyKind(def.kind) + ' ')) +
|
||||||
|
def.name
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toc(top: Top): string[] {
|
||||||
|
return flatten(top.map(({defs}) => walk(defs, toc_one)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function doc_one(def: Def, i: number): string[] {
|
||||||
|
const out = [] as string[]
|
||||||
|
if (def.exported || i > 0) {
|
||||||
|
let indent = ''
|
||||||
|
if (def.children.length == 0) {
|
||||||
|
//const method = (def.kind == 'MethodDeclaration') ? 'method ' : ''
|
||||||
|
out.push('* ' + '**' + def.name + '**: `' + def.type + '`')
|
||||||
|
indent = ' '
|
||||||
|
} else {
|
||||||
|
out.push('### ' + prettyKind(def.kind) + ' ' + def.name)
|
||||||
|
}
|
||||||
|
def.doc.split(/\n\n+/).forEach(s => {
|
||||||
|
out.push('')
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
out.push(indent + '```typescript')
|
||||||
|
}
|
||||||
|
const lines = s.split('\n')
|
||||||
|
lines.forEach(line => out.push(indent + line))
|
||||||
|
if (is_doctest(s)) {
|
||||||
|
out.push(indent + '```')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function doc(top: Top) {
|
||||||
|
return flatten(top.map(({defs}) => walk(defs, doc_one)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const filenames = [] as string[]
|
||||||
|
const argv = process.argv.slice(2)
|
||||||
|
const outputs = [] as ((top: Top) => string[])[]
|
||||||
|
|
||||||
|
{
|
||||||
|
let program: ts.Program
|
||||||
|
let verbose = false
|
||||||
|
for (let i = 0; i < argv.length; i++) {
|
||||||
|
const arg = argv[i]
|
||||||
|
if (arg == '-t' || arg == '--test-script') {
|
||||||
|
outputs.push(test_script)
|
||||||
|
} else if (arg == '-d' || arg == '--doc') {
|
||||||
|
outputs.push(doc)
|
||||||
|
} else if (arg == '--toc' || arg == '--toc') {
|
||||||
|
outputs.push(toc)
|
||||||
|
} else if (arg == '-i' || arg == '--include') {
|
||||||
|
outputs.push(_top => [fs.readFileSync(argv[++i]).toString()])
|
||||||
|
} else if (arg == '-s' || arg == '--string') {
|
||||||
|
outputs.push(_top => [argv[++i]])
|
||||||
|
} else {
|
||||||
|
filenames.push(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputs.length == 0) {
|
||||||
|
console.log(`typescript-doctests <args>
|
||||||
|
Each entry in <args> may be:
|
||||||
|
[-t|--test-script] // write tape test script on stdout
|
||||||
|
[-d|--doc] // write markdown api documentation on stdout
|
||||||
|
[--toc] // write markdown table of contents on stdout
|
||||||
|
[-i|--include] FILENAME // write the contents of a file on stdout
|
||||||
|
[-s|--string] STRING // write a string literally on stdout
|
||||||
|
FILENAME // typescript files to look for docstrings in
|
||||||
|
|
||||||
|
Example usages:
|
||||||
|
|
||||||
|
typescript-doctests src/*.ts -s 'import * as App from "../src/App"' -t > test/App.doctest.ts
|
||||||
|
|
||||||
|
typescript-doctests src/*.ts -i Header.md --toc --doc -i Footer.md > README.md
|
||||||
|
`)
|
||||||
|
process.exit(1)
|
||||||
|
} else {
|
||||||
|
program = ts.createProgram(filenames, {
|
||||||
|
target: ts.ScriptTarget.ES5,
|
||||||
|
module: ts.ModuleKind.CommonJS
|
||||||
|
})
|
||||||
|
const top = generateDocumentation(program, filenames)
|
||||||
|
|
||||||
|
outputs.forEach(m => m(top).forEach(line => console.log(line)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
46
unused-test-files/visitor_test.ts
Normal file
46
unused-test-files/visitor_test.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import * as ts from 'typescript'
|
||||||
|
|
||||||
|
const pwoc = ts.createPrinter({removeComments: true})
|
||||||
|
|
||||||
|
function script(s: string): string {
|
||||||
|
const f = ts.createSourceFile('test.ts', s, ts.ScriptTarget.ES5, true, ts.ScriptKind.TS)
|
||||||
|
const out =
|
||||||
|
f.statements.map(
|
||||||
|
(now, i) => {
|
||||||
|
if (ts.isExpressionStatement(now)) {
|
||||||
|
const next = f.statements[i+1] // zip with next
|
||||||
|
const [a, z] = next ? [next.pos, next.end] : [now.end, f.end]
|
||||||
|
const after = f.text.slice(a, z)
|
||||||
|
const m = after.match(/^\s*\/\/[ \t]*=>([^\n]*)/m)
|
||||||
|
if (m && m[1]) {
|
||||||
|
const lhs = pwoc.printNode(ts.EmitHint.Expression, now.expression, f)
|
||||||
|
const rhs = m[1].trim()
|
||||||
|
return 'assert.deepEqual(' + lhs + ', ' + rhs + ', ' + JSON.stringify(rhs) + ')'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pwoc.printNode(ts.EmitHint.Unspecified, now, f)
|
||||||
|
})
|
||||||
|
return out.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = `
|
||||||
|
const a = 1
|
||||||
|
a
|
||||||
|
// one more
|
||||||
|
// => 1
|
||||||
|
let b = 2
|
||||||
|
a + 1 // => 2
|
||||||
|
// that's all
|
||||||
|
a + b
|
||||||
|
// that's all
|
||||||
|
// => 3
|
||||||
|
function apa(bepa) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
a++
|
||||||
|
b++
|
||||||
|
// hehe // => 5
|
||||||
|
a // => 4
|
||||||
|
`
|
||||||
|
|
||||||
|
console.log(script(s))
|
Loading…
Add table
Add a link
Reference in a new issue