├── package.json ├── README.md └── ltc.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "literally-typed", 3 | "author": { 4 | "name": "Kris Zyp" 5 | }, 6 | "preferGlobal": true, 7 | "main": "./bin/ltc.js", 8 | "bin": { 9 | "tsc": "./bin/tsc" 10 | }, 11 | "dependencies": { 12 | "htmlparser2":"3.7.2", 13 | "typescript": "1.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Literally-Typed is an idea for a [literate](https://en.wikipedia.org/wiki/Literate_programming) form of [TypeScript](http://www.typescriptlang.org/). 2 | This is a concept, and hasn't been implemented, I just have been exploring this idea. 3 | The Literally-Typed language would consist of a set of HTML-style elements that can define a TypeScript interface within 4 | human-targeted documentation, that can be then 5 | be applied to plain JavaScript code to check and enforce type constraints. 6 | 7 | Literally-Typed is based on the premise that type information is essentially a form of documentation, that is machine-readable 8 | (for the purpose of proving code invariants and providing other structured assistance), and consequently belongs with 9 | natural language documentation. By applying Donald Knuth's ideas of literate programming, TypeScript can exist within 10 | your documentation. Combining structured type-based documentation with natural descriptive documentation helps to 11 | unify your documentation, as well enforce consistency in documentation. By using 12 | the Literally-Typed/TypeScript compiler, not only do you gain the code checking providing by typing, 13 | but further consistency is enforced as your documentation can be continuously checked 14 | against your implementation, ensuring that all aspects of your package, including implementation, typing, and 15 | descriptive documentation remain in sync. 16 | 17 | Literally-Typed would also facilitate a clean separation between your implementation which defines the internal steps of 18 | "how" your code works, from the definition of the interface, the descriptions, both in the mechanical 19 | syntax of TypeScript and the natural descriptions of "what" your modules and code provide for external 20 | use. There is no need to have documentation stuffed into implementation code, nor multiple types of documentation. 21 | 22 | Literally-Typed defines HTML elements for the various TypeScript constructs. These can elements can be defined in your 23 | MarkDown documents, or any other type of HTML-based documentation. 24 | 25 | Possible Future Plans 26 | 27 | Based on interest in the project, it may be interesting to also: 28 | * Create TypeScript definition interfaces that can be optionally retained and used by 29 | downstream TypeScript developers. 30 | * Parse AMD and/or CommonJS modules so that full TS inferences can be applied 31 | and checked between modules (right now we are just are enforcing documentation- 32 | originating interfaces on each module). -------------------------------------------------------------------------------- /ltc.js: -------------------------------------------------------------------------------- 1 | var htmlparser = require('htmlparser2'); 2 | var child_process = require('child_process'); 3 | var qfs = require('q-io/fs'); 4 | var all = require('promised-io/promise').all; 5 | function parseHTML(html) { 6 | var outputFiles = []; 7 | var tags = { 8 | interface: function (content) { 9 | tsOutput.push('interface '); 10 | tsOutput.push(content); 11 | tsOutput.push('{\n'); 12 | }, 13 | module: function (content) { 14 | writeCurrentFile(); 15 | // TODO: relativize 16 | dtsFile = content.replace(/\.js^/, '') + '.d.ts'; 17 | }, 18 | function: function (content) { 19 | tsOutput.push(content + ';'); 20 | } 21 | }; 22 | var handler = new htmlparser.DomHandler(function (error, dom) { 23 | if (error) { 24 | console.error('Parsing error', error); 25 | } 26 | else { 27 | visitDom(dom); 28 | writeCurrentFile(); 29 | } 30 | }); 31 | var parser = new htmlparser.Parser(handler); 32 | var dtsFile; 33 | var writes = []; 34 | var tsOutput = []; 35 | var stack = []; 36 | var currentDepth = 0; 37 | parser.write(html); 38 | parser.done(); // this will hold the TypeScript output 39 | function writeCurrentFile() { 40 | if (dtsFile) { 41 | outputFiles.push(dtsFile); 42 | writes.push(qfs.write(dtsFile, tsOutput.join(''))); 43 | } 44 | dtsFile = null; 45 | } 46 | function visitDom(nodes) { 47 | // scan the dom for TypeScript elements 48 | for (var i = 0, l = nodes.length; i < l; i++) { 49 | var node = nodes[i]; 50 | if (node.type === 'tag'){ 51 | // determine hierarchy 52 | var tagName = node.name.toLowerCase(); 53 | var content = node.children[0].data; 54 | var tagHandler = tags[tagName] || tags['x-' + tagName] || tags['ts-' + tagName]; 55 | if (tagHandler) { 56 | tagHandler(content); 57 | } else { 58 | //visitDom(node.children); 59 | } 60 | } 61 | } 62 | } 63 | // combine the cumulative output into a string 64 | return all(writes).then(function(){ 65 | return outputFiles; 66 | }); 67 | } 68 | 69 | function processDirectory(directory) { 70 | qfs.listTree(directory, function (path, stat) { 71 | return !!path.match(/\.md/); 72 | }).then(function (files) { 73 | var outputFiles = []; 74 | return all(files.map(function (file) { 75 | return qfs.read(file).then(function(contents){ 76 | return parseHTML(contents); 77 | }).then(function(outputFilesForContents){ 78 | outputFiles.push.apply(outputFiles, outputFilesForContents); 79 | }); 80 | })).then(function(){ 81 | return outputFiles; 82 | }) 83 | }).then(function(outputFiles){ 84 | // TODO: need to wait for all the files to be written 85 | process.mainModule = { 86 | filename: require.resolve('typescript/bin/tsc.js') 87 | }; 88 | process.argv = ['node', 'tsc'].concat(outputFiles); 89 | require('typescript/bin/tsc'); 90 | return; 91 | var tsc = child_process.fork('node_modules/typescript/bin/tsc', { 92 | env: process.env 93 | }, outputFiles); 94 | tsc.stdout.on('data', function (data) { 95 | console.log('stdout: ' + data); 96 | }); 97 | 98 | tsc.stderr.on('data', function (data) { 99 | console.log('stderr: ' + data); 100 | }); 101 | 102 | tsc.on('close', function (code) { 103 | console.log('child process exited with code ' + code); 104 | }); 105 | tsc.on('error', function (error) { 106 | console.log('child error ', error); 107 | }); 108 | }) 109 | } 110 | 111 | processDirectory('.'); --------------------------------------------------------------------------------