babel-plugin-jsdoc-closure
Advanced tools
Comparing version 1.0.3 to 1.1.0
121
index.js
const parseComment = require('comment-parser'); | ||
const path = require('path'); | ||
let imports, levelsUp, modulePath, resourcePath; | ||
let babel, commentsProperty, imports, levelsUp, modulePath, recast, resourcePath; | ||
@@ -41,2 +41,3 @@ function parseModules(type) { | ||
function processTags(tags, comment) { | ||
let newComment; | ||
tags.forEach(tag => { | ||
@@ -61,3 +62,3 @@ if (tag.tag == 'module') { | ||
} | ||
comment = comment.replace(new RegExp(type, 'g'), replacement); | ||
newComment = comment.value.replace(new RegExp(type, 'g'), replacement); | ||
}); | ||
@@ -67,38 +68,102 @@ } | ||
}); | ||
return comment; | ||
return newComment; | ||
} | ||
module.exports = function(babel) { | ||
function processTypedef(tags, comment) { | ||
let type, typedef, typedefExport, newComment; | ||
for (let i = 0, ii = tags.length; i < ii; ++i) { | ||
const tag = tags[i]; | ||
if (tag.tag == 'typedef') { | ||
typedef = tag; | ||
} else if (tag.tag == 'property') { | ||
if (!type) { | ||
type = {}; | ||
} | ||
type[tag.name] = `(${tag.type})`; | ||
} | ||
} | ||
if (typedef) { | ||
const closureTypedef = type ? JSON.stringify(type).replace(/"/g, '') : typedef.type; | ||
let addLines = comment.value.split('\n').length - 1; | ||
newComment = typedef.source.replace(/(@typedef\s*){[^}]+} .*/, `$1{${closureTypedef}}`); | ||
newComment = `* ${newComment}`; | ||
if (typedef.name) { | ||
addLines--; | ||
typedefExport = `export let ${typedef.name};\n`; | ||
} | ||
while (addLines--) { | ||
newComment += '\n' + (addLines >= 1 ? ' *' : ''); | ||
} | ||
newComment += ' '; | ||
} | ||
return [newComment, typedefExport]; | ||
} | ||
function processComments(property, node, path) { | ||
const comments = node[property]; | ||
for (let i = 0, ii = comments.length; i < ii; ++i) { | ||
let comment = comments[i]; | ||
if (comment.type == 'CommentBlock') { | ||
let tags, modified, typedefExport; | ||
do { | ||
const parsedComment = parseComment(`/*${comment.value}*/`); | ||
if (parsedComment && parsedComment.length > 0) { | ||
tags = parsedComment[0].tags; | ||
const oldComment = comment.value; | ||
let newComment = processTags(tags, comment); | ||
modified = newComment && oldComment != newComment; | ||
if (!newComment && !typedefExport) { | ||
[newComment, typedefExport] = processTypedef(tags, comment); | ||
} | ||
if (newComment) { | ||
if (recast) { | ||
comment = babel.transform(`/*${newComment}*/`).ast.comments[0]; | ||
comments[i] = comment; | ||
} else { | ||
comment.value = newComment; | ||
} | ||
} | ||
if (typedefExport) { | ||
const program = babel.transform(typedefExport).ast.program; | ||
const newNode = program.body[0]; | ||
newNode[commentsProperty] = comments.splice(0, i + 1); | ||
newNode[commentsProperty].forEach(comment => { | ||
comment.leading = true; | ||
}); | ||
i = -1; | ||
ii = comments.length; | ||
if (node.type != 'Program') { | ||
path.insertBefore(newNode); | ||
} else { | ||
path.parent.program.body.push(newNode); | ||
} | ||
typedefExport = undefined; | ||
newComment = undefined; | ||
} | ||
} | ||
} while (modified); | ||
} | ||
} | ||
} | ||
module.exports = function(b) { | ||
babel = b; | ||
return { | ||
visitor: { | ||
Program: function(path, state) { | ||
const recast = state.file.opts.parserOpts && state.file.opts.parserOpts.parser == 'recast'; | ||
recast = state.file.opts.parserOpts && state.file.opts.parserOpts.parser == 'recast'; | ||
commentsProperty = recast ? 'comments' : 'leadingComments'; | ||
resourcePath = state.file.opts.filename; | ||
imports = {}; | ||
const root = path.node; | ||
const innerCommentsProperty = recast ? 'comments' : 'innerComments'; | ||
if (root[innerCommentsProperty]) { | ||
processComments(innerCommentsProperty, root, path); | ||
} | ||
path.traverse({ | ||
enter(path) { | ||
const comments = recast ? path.node.comments : path.node.leadingComments; | ||
if (comments) { | ||
comments.forEach((comment, i) => { | ||
if (comment.type == 'CommentBlock') { | ||
let tags, modified; | ||
do { | ||
const parsedComment = parseComment(`/*${comment.value}*/`); | ||
if (parsedComment && parsedComment.length > 0) { | ||
tags = parsedComment[0].tags; | ||
const newValue = processTags(tags, comment.value); | ||
modified = newValue !== comment.value; | ||
if (modified) { | ||
if (recast) { | ||
comments[i] = babel.transform(`/*${newValue}*/`).ast.comments[0]; | ||
comment = comments[i]; | ||
} else { | ||
comment.value = newValue; | ||
} | ||
} | ||
} | ||
} while (modified); | ||
} | ||
}); | ||
if (path.node[commentsProperty]) { | ||
processComments(commentsProperty, path.node, path); | ||
} | ||
@@ -105,0 +170,0 @@ } |
{ | ||
"name": "babel-plugin-jsdoc-closure", | ||
"version": "1.0.3", | ||
"version": "1.1.0", | ||
"description": "Transpiles JSDoc types from namepaths to types for Closure Compiler", | ||
"main": "index.js", | ||
"scripts": { | ||
"lint": "eslint index.html test/", | ||
"lint": "eslint index.js test/", | ||
"test": "npm run lint && mocha" | ||
@@ -9,0 +9,0 @@ }, |
@@ -79,2 +79,4 @@ # babel-plugin-jsdoc-closure | ||
### Convert module namepaths to imported types | ||
Closure Compiler does not allow JSDoc's [namepaths](http://usejsdoc.org/about-namepaths.html) with [module identifiers](http://usejsdoc.org/howto-commonjs-modules.html#module-identifiers) as types. Instead, with `module_resolution: 'NODE'`, it recognizes types that are imported from other files. Let's say you have a file `foo/Bar.js` with the following: | ||
@@ -116,2 +118,25 @@ | ||
**Note**: To avoid the need for source maps, line numbers are retained by this plugin. This is the reason why the `require()` assignments are added at the bottom of each file. | ||
### Convert JSDoc typedefs to Closure typedefs | ||
JSDoc uses a nice, documentable format for `{Object}` typedefs: | ||
```js | ||
/** | ||
* @typedef {Object} Foo | ||
* @property {string} bar Bar. | ||
* @property {module:types.Baz} baz Baz. | ||
*/ | ||
``` | ||
Such typedefs are not understood by Closure compiler, so they are transformed to something like | ||
```js | ||
/** | ||
* @typedef {{bar: (string), baz: (_types_Baz)}} | ||
*/ | ||
export let Foo; | ||
``` | ||
### Notes | ||
To avoid the need for source maps, line numbers are retained by this plugin. This is the reason why the `require()` assignments are added at the bottom of each file. |
@@ -24,3 +24,3 @@ const babel = require('babel-core'); | ||
let got = babel.transform(source, filename ? Object.assign({filename}, options) : options); | ||
assert.equal(got.code, expected); | ||
assert.equal(got.code.replace(/[\n\s]+/g, ''), expected.replace(/[\n\s]+/g, '')); | ||
got = babel.transform(source, filename ? Object.assign({filename}, recastOptions) : recastOptions); | ||
@@ -121,2 +121,41 @@ assert.equal(got.code, expected); | ||
it('exports typedefs', function() { | ||
test( | ||
'/** @module module2/types */\n' + | ||
'/**\n' + | ||
' * @typedef {number} Foo\n' + | ||
' */\n', | ||
'/** @module module2/types */\n' + | ||
'/** @typedef {number}\n' + | ||
' */\n' + | ||
'export let Foo;', | ||
'./test/module2/types.js' | ||
); | ||
}); | ||
it('modifies Object typedefs', function() { | ||
test( | ||
'/** @module module2/types */\n' + | ||
'/**\n' + | ||
' * @typedef {number} Bar\n' + | ||
' */\n' + | ||
'/**\n' + | ||
' * @typedef {Object} Foo\n' + | ||
' * @property {!module:module1/Bar} bar Bar.\n' + | ||
' * @property {number} baz Baz.\n' + | ||
' */\n', | ||
'/** @module module2/types */\n' + | ||
'/** @typedef {number}\n' + | ||
' */\n' + | ||
'export let Bar;\n\n' + | ||
'/** @typedef {{bar:(!module1$Bar),baz:(number)}}\n' + | ||
' *\n' + | ||
' *\n' + | ||
' */\n' + | ||
'export let Foo;\n\n' + | ||
'const module1$Bar = require(\'../module1/Bar\');', | ||
'./test/module2/types.js' | ||
); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15782
310
141