Skip to content
This repository has been archived by the owner on May 1, 2019. It is now read-only.

Commit

Permalink
Analyze @global functions, and respect @function name override.
Browse files Browse the repository at this point in the history
  • Loading branch information
aomarks committed Nov 30, 2017
1 parent f23ad40 commit d87f1dd
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 29 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
<!-- Add new, unreleased changes here. -->

* Functions will now be scanned if they have a `@global` annotation. Previously they would only be scanned if they had a `@memberof` annotation. One of these annotations is required because otherwise a lot of functions that aren't really public are included in the analysis (e.g. because they are hidden due to their scoping).
* Function names can now be overridden with e.g. `@function MyNewName`.

## [3.0.0-pre.1] - 2017-11-29

* [BREAKING] Switched the underlying parser/AST for JavaScript from `espree/estree` to `babylon/babel-types`. This was needed to support parsing of important platform features such as dynamic imports and moves us closer to supporting TypeScript.
Expand Down
22 changes: 18 additions & 4 deletions src/javascript/function-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,24 +121,38 @@ class FunctionVisitor implements Visitor {
private _initFunction(
node: babel.Node, analyzedName?: string, _fn?: babel.Function) {
const comment = getAttachedComment(node);

// Quickly filter down to potential candidates.
if (!comment || comment.indexOf('@memberof') === -1) {
if (!comment) {
return;
}
const docs = jsdoc.parseJsdoc(comment);

// The @function annotation can override the name.
const functionTag = jsdoc.getTag(docs, 'function');
if (functionTag && functionTag.name) {
analyzedName = functionTag.name;
}

if (!analyzedName) {
// TODO(fks): Propagate a warning if name could not be determined
return;
}

const docs = jsdoc.parseJsdoc(comment);
if (!jsdoc.hasTag(docs, 'global') && !jsdoc.hasTag(docs, 'memberof')) {
// Without this check we would emit a lot of functions not worthy of
// inclusion. Since we don't do scope analysis, we can't tell when a
// function is actually part of an exposed API. Only include functions
// that are explicitly @global, or declared as part of some namespace
// with @memberof.
return;
}

// TODO(justinfagnani): remove polymerMixin support
if (jsdoc.hasTag(docs, 'mixinFunction') ||
jsdoc.hasTag(docs, 'polymerMixin')) {
// This is a mixin, not a normal function.
return;
}

const functionName = getNamespacedIdentifier(analyzedName, docs);
const sourceRange = this.document.sourceRangeForNode(node)!;
const returnTag = jsdoc.getTag(docs, 'return');
Expand Down
70 changes: 45 additions & 25 deletions src/test/javascript/function-scanner_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ suite('FunctionScanner', () => {
const urlLoader = new FSUrlLoader(testFilesDir);
const underliner = new CodeUnderliner(urlLoader);

async function getNamespaceFunctions(filename: string): Promise<any[]> {
async function getNamespaceFunctions(filename: string):
Promise<ScannedFunction[]> {
const file = await urlLoader.load(filename);
const parser = new JavaScriptParser();
const document = parser.parse(file, filename as ResolvedUrl);
Expand All @@ -42,20 +43,20 @@ suite('FunctionScanner', () => {
};


async function getTestProps(fn: ScannedFunction):
Promise<any> {
return {
name: fn.name,
description: fn.description,
summary: fn.summary,
warnings: fn.warnings,
params: fn.params, return: fn.return,
codeSnippet: await underliner.underline(fn.sourceRange),
privacy: fn.privacy
};
}

test('scans', async() => {
async function getTestProps(fn: ScannedFunction): Promise<any> {
return {
name: fn.name,
description: fn.description,
summary: fn.summary,
warnings: fn.warnings,
params: fn.params,
return: fn.return,
codeSnippet: await underliner.underline(fn.sourceRange),
privacy: fn.privacy
};
}

test('handles @memberof annotation', async () => {
const namespaceFunctions =
await getNamespaceFunctions('memberof-functions.js');
const functionData =
Expand All @@ -71,7 +72,8 @@ suite('FunctionScanner', () => {
name: 'a',
type: 'Number',
}],
privacy: 'public', return: undefined,
privacy: 'public',
return: undefined,
codeSnippet: `
function aaa(a) {
~~~~~~~~~~~~~~~~~
Expand All @@ -85,7 +87,8 @@ function aaa(a) {
description: 'bbb',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'public',
codeSnippet: `
Polymer.bbb = function bbb() {
Expand All @@ -100,7 +103,8 @@ Polymer.bbb = function bbb() {
description: 'ccc',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'protected',
codeSnippet: `
function ccc() {
Expand All @@ -114,7 +118,8 @@ Polymer.bbb = function bbb() {
summary: '',
warnings: [],
privacy: 'protected',
params: [], return: undefined,
params: [],
return: undefined,
codeSnippet: `
_ddd: function() {
~~~~~~~~~~~~~~~~~~
Expand All @@ -128,7 +133,8 @@ Polymer.bbb = function bbb() {
description: 'eee',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'private',
codeSnippet: `
eee: () => {},
Expand All @@ -139,7 +145,8 @@ Polymer.bbb = function bbb() {
description: 'fff',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'public',
codeSnippet: `
fff() {
Expand All @@ -154,7 +161,8 @@ Polymer.bbb = function bbb() {
description: 'ggg',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'public',
codeSnippet: `
ggg: someFunction,
Expand All @@ -165,7 +173,8 @@ Polymer.bbb = function bbb() {
description: 'hhh_ should be private',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'private',
codeSnippet: `
hhh_: someOtherFunc,
Expand All @@ -176,7 +185,8 @@ Polymer.bbb = function bbb() {
description: '__iii should be private too',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'private',
codeSnippet: `
__iii() { },
Expand All @@ -187,7 +197,8 @@ Polymer.bbb = function bbb() {
description: 'jjj',
summary: '',
warnings: [],
params: [], return: undefined,
params: [],
return: undefined,
privacy: 'public',
codeSnippet: `
var jjj = function() {
Expand All @@ -199,4 +210,13 @@ var jjj = function() {
},
]);
});

test('handles @global, @memberof, @function annotations', async () => {
const functions = await getNamespaceFunctions('annotated-functions.js');
assert.deepEqual(functions.map((fn) => fn.name), [
'globalFn',
'SomeNamespace.memberofFn',
'overrideNameFn',
]);
});
});
23 changes: 23 additions & 0 deletions src/test/static/namespaces/annotated-functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Ignore me because I have no annotation to hint that I'm in a public API.
*/
function ignoreFn() { }

/**
* I'm explicitly global.
* @global
*/
function globalFn() { }

/**
* I'm in a namespace, so I'm probably part of a public API.
* @memberof SomeNamespace
*/
function memberofFn() { }

/**
* My @function annotation overrides my name.
* @global
* @function overrideNameFn
*/
function wrongNameFn() { }

0 comments on commit d87f1dd

Please sign in to comment.