Skip to content

Commit c32d557

Browse files
rubystargos
authored andcommitted
tools: add [src] links to assert.html
Parse `const assert = module.exports = ok;` as exporting a constructor named `assert`. PR-URL: #22601 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent b2a955a commit c32d557

File tree

3 files changed

+76
-27
lines changed

3 files changed

+76
-27
lines changed

test/fixtures/apilinks/reverse.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
// Parallel assignment to the exported variable and module.exports.
4+
5+
function ok() {
6+
}
7+
8+
const asserts = module.exports = ok;
9+
10+
asserts.ok = ok;
11+
12+
asserts.strictEqual = function() {
13+
}

test/fixtures/apilinks/reverse.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"asserts": "reverse.js#L8",
3+
"asserts.ok": "reverse.js#L5",
4+
"asserts.strictEqual": "reverse.js#L12"
5+
}

tools/doc/apilinks.js

+58-27
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,50 @@ process.argv.slice(2).forEach((file) => {
5555
const ast = acorn.parse(source, { ecmaVersion: 10, locations: true });
5656
const program = ast.body;
5757

58+
// Build link
59+
const link = `https://github.com/${repo}/blob/${tag}/` +
60+
path.relative('.', file).replace(/\\/g, '/');
61+
5862
// Scan for exports.
5963
const exported = { constructors: [], identifiers: [] };
6064
program.forEach((statement) => {
61-
if (statement.type !== 'ExpressionStatement') return;
62-
const expr = statement.expression;
63-
if (expr.type !== 'AssignmentExpression') return;
64-
65-
let lhs = expr.left;
66-
if (expr.left.object.type === 'MemberExpression') lhs = lhs.object;
67-
if (lhs.type !== 'MemberExpression') return;
68-
if (lhs.object.name !== 'module') return;
69-
if (lhs.property.name !== 'exports') return;
70-
71-
let rhs = expr.right;
72-
while (rhs.type === 'AssignmentExpression') rhs = rhs.right;
73-
74-
if (rhs.type === 'NewExpression') {
75-
exported.constructors.push(rhs.callee.name);
76-
} else if (rhs.type === 'ObjectExpression') {
77-
rhs.properties.forEach((property) => {
78-
if (property.value.type === 'Identifier') {
79-
exported.identifiers.push(property.value.name);
80-
if (/^[A-Z]/.test(property.value.name[0])) {
81-
exported.constructors.push(property.value.name);
65+
if (statement.type === 'ExpressionStatement') {
66+
const expr = statement.expression;
67+
if (expr.type !== 'AssignmentExpression') return;
68+
69+
let lhs = expr.left;
70+
if (expr.left.object.type === 'MemberExpression') lhs = lhs.object;
71+
if (lhs.type !== 'MemberExpression') return;
72+
if (lhs.object.name !== 'module') return;
73+
if (lhs.property.name !== 'exports') return;
74+
75+
let rhs = expr.right;
76+
while (rhs.type === 'AssignmentExpression') rhs = rhs.right;
77+
78+
if (rhs.type === 'NewExpression') {
79+
exported.constructors.push(rhs.callee.name);
80+
} else if (rhs.type === 'ObjectExpression') {
81+
rhs.properties.forEach((property) => {
82+
if (property.value.type === 'Identifier') {
83+
exported.identifiers.push(property.value.name);
84+
if (/^[A-Z]/.test(property.value.name[0])) {
85+
exported.constructors.push(property.value.name);
86+
}
8287
}
83-
}
84-
});
85-
} else if (rhs.type === 'Identifier') {
86-
exported.identifiers.push(rhs.name);
88+
});
89+
} else if (rhs.type === 'Identifier') {
90+
exported.identifiers.push(rhs.name);
91+
}
92+
} else if (statement.type === 'VariableDeclaration') {
93+
for (const decl of statement.declarations) {
94+
let init = decl.init;
95+
while (init && init.type === 'AssignmentExpression') init = init.left;
96+
if (!init || init.type !== 'MemberExpression') continue;
97+
if (init.object.name !== 'module') continue;
98+
if (init.property.name !== 'exports') continue;
99+
exported.constructors.push(decl.id.name);
100+
definition[decl.id.name] = `${link}#L${statement.loc.start.line}`;
101+
}
87102
}
88103
});
89104

@@ -93,8 +108,7 @@ process.argv.slice(2).forEach((file) => {
93108
// ClassName.prototype.foo = ...;
94109
// function Identifier(...) {...};
95110
//
96-
const link = `https://github.com/${repo}/blob/${tag}/` +
97-
path.relative('.', file).replace(/\\/g, '/');
111+
const indirect = {};
98112

99113
program.forEach((statement) => {
100114
if (statement.type === 'ExpressionStatement') {
@@ -128,6 +142,11 @@ process.argv.slice(2).forEach((file) => {
128142
}
129143

130144
definition[name] = `${link}#L${statement.loc.start.line}`;
145+
146+
if (expr.left.property.name === expr.right.name) {
147+
indirect[expr.right.name] = name;
148+
}
149+
131150
} else if (statement.type === 'FunctionDeclaration') {
132151
const name = statement.id.name;
133152
if (!exported.identifiers.includes(name)) return;
@@ -136,6 +155,18 @@ process.argv.slice(2).forEach((file) => {
136155
`${link}#L${statement.loc.start.line}`;
137156
}
138157
});
158+
159+
// Search for indirect references of the form ClassName.foo = foo;
160+
if (Object.keys(indirect).length > 0) {
161+
program.forEach((statement) => {
162+
if (statement.type === 'FunctionDeclaration') {
163+
const name = statement.id.name;
164+
if (indirect[name]) {
165+
definition[indirect[name]] = `${link}#L${statement.loc.start.line}`;
166+
}
167+
}
168+
});
169+
}
139170
});
140171

141172
console.log(JSON.stringify(definition, null, 2));

0 commit comments

Comments
 (0)