From 40e96e5aec1b88a7774afd29233474b36df5965f Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Thu, 30 Aug 2018 10:37:04 -0400 Subject: [PATCH 1/2] tools: add [src] links to assert.html Parse `const assert = module.exports = ok;` as exporting a constructor named `assert`. --- test/fixtures/apilinks/reverse.js | 10 +++++ test/fixtures/apilinks/reverse.json | 3 ++ tools/doc/apilinks.js | 60 +++++++++++++++++------------ 3 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/apilinks/reverse.js create mode 100644 test/fixtures/apilinks/reverse.json diff --git a/test/fixtures/apilinks/reverse.js b/test/fixtures/apilinks/reverse.js new file mode 100644 index 00000000000000..be25e617500006 --- /dev/null +++ b/test/fixtures/apilinks/reverse.js @@ -0,0 +1,10 @@ +'use strict'; + +// Parallel assignment to the exported variable and module.exports. + +function ok() { +} + +const asserts = module.exports = ok; + +asserts.ok = ok; diff --git a/test/fixtures/apilinks/reverse.json b/test/fixtures/apilinks/reverse.json new file mode 100644 index 00000000000000..bdae2a8c8dbd31 --- /dev/null +++ b/test/fixtures/apilinks/reverse.json @@ -0,0 +1,3 @@ +{ + "asserts.ok": "reverse.js#L10" +} diff --git a/tools/doc/apilinks.js b/tools/doc/apilinks.js index 98dd7827d67ac0..c6b53c580d1cb4 100644 --- a/tools/doc/apilinks.js +++ b/tools/doc/apilinks.js @@ -58,32 +58,42 @@ process.argv.slice(2).forEach((file) => { // Scan for exports. const exported = { constructors: [], identifiers: [] }; program.forEach((statement) => { - if (statement.type !== 'ExpressionStatement') return; - const expr = statement.expression; - if (expr.type !== 'AssignmentExpression') return; - - let lhs = expr.left; - if (expr.left.object.type === 'MemberExpression') lhs = lhs.object; - if (lhs.type !== 'MemberExpression') return; - if (lhs.object.name !== 'module') return; - if (lhs.property.name !== 'exports') return; - - let rhs = expr.right; - while (rhs.type === 'AssignmentExpression') rhs = rhs.right; - - if (rhs.type === 'NewExpression') { - exported.constructors.push(rhs.callee.name); - } else if (rhs.type === 'ObjectExpression') { - rhs.properties.forEach((property) => { - if (property.value.type === 'Identifier') { - exported.identifiers.push(property.value.name); - if (/^[A-Z]/.test(property.value.name[0])) { - exported.constructors.push(property.value.name); + if (statement.type === 'ExpressionStatement') { + const expr = statement.expression; + if (expr.type !== 'AssignmentExpression') return; + + let lhs = expr.left; + if (expr.left.object.type === 'MemberExpression') lhs = lhs.object; + if (lhs.type !== 'MemberExpression') return; + if (lhs.object.name !== 'module') return; + if (lhs.property.name !== 'exports') return; + + let rhs = expr.right; + while (rhs.type === 'AssignmentExpression') rhs = rhs.right; + + if (rhs.type === 'NewExpression') { + exported.constructors.push(rhs.callee.name); + } else if (rhs.type === 'ObjectExpression') { + rhs.properties.forEach((property) => { + if (property.value.type === 'Identifier') { + exported.identifiers.push(property.value.name); + if (/^[A-Z]/.test(property.value.name[0])) { + exported.constructors.push(property.value.name); + } } - } - }); - } else if (rhs.type === 'Identifier') { - exported.identifiers.push(rhs.name); + }); + } else if (rhs.type === 'Identifier') { + exported.identifiers.push(rhs.name); + } + } else if (statement.type === 'VariableDeclaration') { + for (const decl of statement.declarations) { + let init = decl.init; + while (init && init.type === 'AssignmentExpression') init = init.left; + if (!init || init.type !== 'MemberExpression') continue; + if (init.object.name !== 'module') continue; + if (init.property.name !== 'exports') continue; + exported.constructors.push(decl.id.name); + } } }); From 62fc53b4615a93490804101ad9da4d9a59c0a31e Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Thu, 30 Aug 2018 14:40:01 -0400 Subject: [PATCH 2/2] [squashable] indirect and module reverse defintions Hard to explain, see test case for examples. :-) --- test/fixtures/apilinks/reverse.js | 3 +++ test/fixtures/apilinks/reverse.json | 4 +++- tools/doc/apilinks.js | 25 +++++++++++++++++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/test/fixtures/apilinks/reverse.js b/test/fixtures/apilinks/reverse.js index be25e617500006..5a61e50dc27581 100644 --- a/test/fixtures/apilinks/reverse.js +++ b/test/fixtures/apilinks/reverse.js @@ -8,3 +8,6 @@ function ok() { const asserts = module.exports = ok; asserts.ok = ok; + +asserts.strictEqual = function() { +} diff --git a/test/fixtures/apilinks/reverse.json b/test/fixtures/apilinks/reverse.json index bdae2a8c8dbd31..aa32e4a90a745c 100644 --- a/test/fixtures/apilinks/reverse.json +++ b/test/fixtures/apilinks/reverse.json @@ -1,3 +1,5 @@ { - "asserts.ok": "reverse.js#L10" + "asserts": "reverse.js#L8", + "asserts.ok": "reverse.js#L5", + "asserts.strictEqual": "reverse.js#L12" } diff --git a/tools/doc/apilinks.js b/tools/doc/apilinks.js index c6b53c580d1cb4..35183912d31870 100644 --- a/tools/doc/apilinks.js +++ b/tools/doc/apilinks.js @@ -55,6 +55,10 @@ process.argv.slice(2).forEach((file) => { const ast = acorn.parse(source, { ecmaVersion: 10, locations: true }); const program = ast.body; + // Build link + const link = `https://github.com/${repo}/blob/${tag}/` + + path.relative('.', file).replace(/\\/g, '/'); + // Scan for exports. const exported = { constructors: [], identifiers: [] }; program.forEach((statement) => { @@ -93,6 +97,7 @@ process.argv.slice(2).forEach((file) => { if (init.object.name !== 'module') continue; if (init.property.name !== 'exports') continue; exported.constructors.push(decl.id.name); + definition[decl.id.name] = `${link}#L${statement.loc.start.line}`; } } }); @@ -103,8 +108,7 @@ process.argv.slice(2).forEach((file) => { // ClassName.prototype.foo = ...; // function Identifier(...) {...}; // - const link = `https://github.com/${repo}/blob/${tag}/` + - path.relative('.', file).replace(/\\/g, '/'); + const indirect = {}; program.forEach((statement) => { if (statement.type === 'ExpressionStatement') { @@ -138,6 +142,11 @@ process.argv.slice(2).forEach((file) => { } definition[name] = `${link}#L${statement.loc.start.line}`; + + if (expr.left.property.name === expr.right.name) { + indirect[expr.right.name] = name; + } + } else if (statement.type === 'FunctionDeclaration') { const name = statement.id.name; if (!exported.identifiers.includes(name)) return; @@ -146,6 +155,18 @@ process.argv.slice(2).forEach((file) => { `${link}#L${statement.loc.start.line}`; } }); + + // Search for indirect references of the form ClassName.foo = foo; + if (Object.keys(indirect).length > 0) { + program.forEach((statement) => { + if (statement.type === 'FunctionDeclaration') { + const name = statement.id.name; + if (indirect[name]) { + definition[indirect[name]] = `${link}#L${statement.loc.start.line}`; + } + } + }); + } }); console.log(JSON.stringify(definition, null, 2));