Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

babel-plugin-jsdoc-closure

Package Overview
Dependencies
Maintainers
2
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babel-plugin-jsdoc-closure - npm Package Compare versions

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'
);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc