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

Commit 5f67150

Browse files
authored
Merge pull request #774 from Polymer/fn-annotations
Analyze @global functions, and respect @function name override.
2 parents 8fd7fc3 + d87f1dd commit 5f67150

10 files changed

+183
-132
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99
<!-- Add new, unreleased changes here. -->
1010
* Added `js-import` feature with `lazy: true` for dynamic imports call expressions of the form `import()`.
1111

12+
* 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).
13+
* Function names can now be overridden with e.g. `@function MyNewName`.
14+
1215
## [3.0.0-pre.1] - 2017-11-29
1316

1417
* [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.

src/javascript/function-scanner.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -121,24 +121,38 @@ class FunctionVisitor implements Visitor {
121121
private _initFunction(
122122
node: babel.Node, analyzedName?: string, _fn?: babel.Function) {
123123
const comment = getAttachedComment(node);
124-
125-
// Quickly filter down to potential candidates.
126-
if (!comment || comment.indexOf('@memberof') === -1) {
124+
if (!comment) {
127125
return;
128126
}
127+
const docs = jsdoc.parseJsdoc(comment);
128+
129+
// The @function annotation can override the name.
130+
const functionTag = jsdoc.getTag(docs, 'function');
131+
if (functionTag && functionTag.name) {
132+
analyzedName = functionTag.name;
133+
}
129134

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

135-
const docs = jsdoc.parseJsdoc(comment);
140+
if (!jsdoc.hasTag(docs, 'global') && !jsdoc.hasTag(docs, 'memberof')) {
141+
// Without this check we would emit a lot of functions not worthy of
142+
// inclusion. Since we don't do scope analysis, we can't tell when a
143+
// function is actually part of an exposed API. Only include functions
144+
// that are explicitly @global, or declared as part of some namespace
145+
// with @memberof.
146+
return;
147+
}
148+
136149
// TODO(justinfagnani): remove polymerMixin support
137150
if (jsdoc.hasTag(docs, 'mixinFunction') ||
138151
jsdoc.hasTag(docs, 'polymerMixin')) {
139152
// This is a mixin, not a normal function.
140153
return;
141154
}
155+
142156
const functionName = getNamespacedIdentifier(analyzedName, docs);
143157
const sourceRange = this.document.sourceRangeForNode(node)!;
144158
const returnTag = jsdoc.getTag(docs, 'return');

src/test/javascript/function-scanner_test.ts

+45-25
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ suite('FunctionScanner', () => {
2929
const urlLoader = new FSUrlLoader(testFilesDir);
3030
const underliner = new CodeUnderliner(urlLoader);
3131

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

4445

45-
async function getTestProps(fn: ScannedFunction):
46-
Promise<any> {
47-
return {
48-
name: fn.name,
49-
description: fn.description,
50-
summary: fn.summary,
51-
warnings: fn.warnings,
52-
params: fn.params, return: fn.return,
53-
codeSnippet: await underliner.underline(fn.sourceRange),
54-
privacy: fn.privacy
55-
};
56-
}
57-
58-
test('scans', async() => {
46+
async function getTestProps(fn: ScannedFunction): Promise<any> {
47+
return {
48+
name: fn.name,
49+
description: fn.description,
50+
summary: fn.summary,
51+
warnings: fn.warnings,
52+
params: fn.params,
53+
return: fn.return,
54+
codeSnippet: await underliner.underline(fn.sourceRange),
55+
privacy: fn.privacy
56+
};
57+
}
58+
59+
test('handles @memberof annotation', async () => {
5960
const namespaceFunctions =
6061
await getNamespaceFunctions('memberof-functions.js');
6162
const functionData =
@@ -71,7 +72,8 @@ suite('FunctionScanner', () => {
7172
name: 'a',
7273
type: 'Number',
7374
}],
74-
privacy: 'public', return: undefined,
75+
privacy: 'public',
76+
return: undefined,
7577
codeSnippet: `
7678
function aaa(a) {
7779
~~~~~~~~~~~~~~~~~
@@ -85,7 +87,8 @@ function aaa(a) {
8587
description: 'bbb',
8688
summary: '',
8789
warnings: [],
88-
params: [], return: undefined,
90+
params: [],
91+
return: undefined,
8992
privacy: 'public',
9093
codeSnippet: `
9194
Polymer.bbb = function bbb() {
@@ -100,7 +103,8 @@ Polymer.bbb = function bbb() {
100103
description: 'ccc',
101104
summary: '',
102105
warnings: [],
103-
params: [], return: undefined,
106+
params: [],
107+
return: undefined,
104108
privacy: 'protected',
105109
codeSnippet: `
106110
function ccc() {
@@ -114,7 +118,8 @@ Polymer.bbb = function bbb() {
114118
summary: '',
115119
warnings: [],
116120
privacy: 'protected',
117-
params: [], return: undefined,
121+
params: [],
122+
return: undefined,
118123
codeSnippet: `
119124
_ddd: function() {
120125
~~~~~~~~~~~~~~~~~~
@@ -128,7 +133,8 @@ Polymer.bbb = function bbb() {
128133
description: 'eee',
129134
summary: '',
130135
warnings: [],
131-
params: [], return: undefined,
136+
params: [],
137+
return: undefined,
132138
privacy: 'private',
133139
codeSnippet: `
134140
eee: () => {},
@@ -139,7 +145,8 @@ Polymer.bbb = function bbb() {
139145
description: 'fff',
140146
summary: '',
141147
warnings: [],
142-
params: [], return: undefined,
148+
params: [],
149+
return: undefined,
143150
privacy: 'public',
144151
codeSnippet: `
145152
fff() {
@@ -154,7 +161,8 @@ Polymer.bbb = function bbb() {
154161
description: 'ggg',
155162
summary: '',
156163
warnings: [],
157-
params: [], return: undefined,
164+
params: [],
165+
return: undefined,
158166
privacy: 'public',
159167
codeSnippet: `
160168
ggg: someFunction,
@@ -165,7 +173,8 @@ Polymer.bbb = function bbb() {
165173
description: 'hhh_ should be private',
166174
summary: '',
167175
warnings: [],
168-
params: [], return: undefined,
176+
params: [],
177+
return: undefined,
169178
privacy: 'private',
170179
codeSnippet: `
171180
hhh_: someOtherFunc,
@@ -176,7 +185,8 @@ Polymer.bbb = function bbb() {
176185
description: '__iii should be private too',
177186
summary: '',
178187
warnings: [],
179-
params: [], return: undefined,
188+
params: [],
189+
return: undefined,
180190
privacy: 'private',
181191
codeSnippet: `
182192
__iii() { },
@@ -187,7 +197,8 @@ Polymer.bbb = function bbb() {
187197
description: 'jjj',
188198
summary: '',
189199
warnings: [],
190-
params: [], return: undefined,
200+
params: [],
201+
return: undefined,
191202
privacy: 'public',
192203
codeSnippet: `
193204
var jjj = function() {
@@ -199,4 +210,13 @@ var jjj = function() {
199210
},
200211
]);
201212
});
213+
214+
test('handles @global, @memberof, @function annotations', async () => {
215+
const functions = await getNamespaceFunctions('annotated-functions.js');
216+
assert.deepEqual(functions.map((fn) => fn.name), [
217+
'globalFn',
218+
'SomeNamespace.memberofFn',
219+
'overrideNameFn',
220+
]);
221+
});
202222
});

src/test/polymer/behavior-scanner_test.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ suite('BehaviorScanner', () => {
109109
throw new Error('Could not find Polymer.SimpleNamespacedBehavior');
110110
}
111111
assert.deepEqual(
112-
[...behavior.methods.keys()],
113-
['method', 'shorthandMethod']);
112+
[...behavior.methods.keys()], ['method', 'shorthandMethod']);
114113
assert.deepEqual(
115114
[...behavior.properties.keys()],
116115
['simple', 'object', 'array', 'attached', 'templateLiteral']);

src/test/polymer/polymer-core-feature_test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {FSUrlLoader} from '../../url-loader/fs-url-loader';
2424

2525
suite('PolymerCoreFeatureScanner', () => {
2626

27-
test('scans _addFeature calls and the Polymer.Base assignment', async () => {
27+
test('scans _addFeature calls and the Polymer.Base assignment', async() => {
2828
const js = `
2929
/** Feature A */
3030
Polymer.Base._addFeature({
@@ -116,7 +116,7 @@ suite('PolymerCoreFeatureScanner', () => {
116116
assert.lengthOf(invalid.warnings, 1);
117117
});
118118

119-
test('resolves the Polymer.Base class', async () => {
119+
test('resolves the Polymer.Base class', async() => {
120120
const analyzer = new Analyzer({
121121
urlLoader: new FSUrlLoader(
122122
// This directory contains files copied from Polymer 1.x core.

src/test/polymer/polymer-element_test.ts

+15-20
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,16 @@ suite('PolymerElement', () => {
5858
attributes: Array.from(element.attributes.values()).map((a) => ({
5959
name: a.name,
6060
})),
61-
methods: Array.from(element.methods.values()).map((m) => ({
62-
name: m.name,
63-
params: m.params,
64-
return: m.return,
65-
inheritedFrom:
66-
m.inheritedFrom
67-
})),
61+
methods: Array.from(element.methods.values())
62+
.map((m) => ({
63+
name: m.name,
64+
params: m.params, return: m.return,
65+
inheritedFrom: m.inheritedFrom
66+
})),
6867
};
6968
}
7069

71-
test('Scans and resolves base and sub-class', async () => {
70+
test('Scans and resolves base and sub-class', async() => {
7271
const elements = await getElements('test-element-3.js');
7372
const elementData = Array.from(elements).map(getTestProps);
7473
assert.deepEqual(elementData, [
@@ -132,7 +131,7 @@ suite('PolymerElement', () => {
132131
]);
133132
});
134133

135-
test('Computes correct property information', async () => {
134+
test('Computes correct property information', async() => {
136135
const elements = await getElements('test-element-17.js');
137136
const elementData = Array.from(elements).map(getTestProps);
138137
assert.deepEqual(elementData, [
@@ -156,7 +155,7 @@ suite('PolymerElement', () => {
156155
]);
157156
});
158157

159-
test('Elements inherit from mixins and base classes', async () => {
158+
test('Elements inherit from mixins and base classes', async() => {
160159
const elements = await getElements('test-element-7.js');
161160
const elementData = Array.from(elements).map(getTestProps);
162161
assert.deepEqual(elementData, [
@@ -185,8 +184,7 @@ suite('PolymerElement', () => {
185184
],
186185
methods: [{
187186
name: 'customMethodOnBaseElement',
188-
params: [],
189-
return: undefined,
187+
params: [], return: undefined,
190188
inheritedFrom: undefined
191189
}],
192190
},
@@ -237,20 +235,17 @@ suite('PolymerElement', () => {
237235
methods: [
238236
{
239237
name: 'customMethodOnBaseElement',
240-
params: [],
241-
return: undefined,
238+
params: [], return: undefined,
242239
inheritedFrom: 'BaseElement'
243240
},
244241
{
245242
name: 'customMethodOnMixin',
246-
params: [],
247-
return: undefined,
243+
params: [], return: undefined,
248244
inheritedFrom: 'Mixin'
249245
},
250246
{
251247
name: 'customMethodOnSubElement',
252-
params: [],
253-
return: undefined,
248+
params: [], return: undefined,
254249
inheritedFrom: undefined
255250
},
256251
],
@@ -267,14 +262,14 @@ suite('PolymerElement', () => {
267262
return [...elements][0]!;
268263
}
269264

270-
test('Elements with only one doc comment have no warning', async () => {
265+
test('Elements with only one doc comment have no warning', async() => {
271266
const element = await getElement('test-element-14.html');
272267
const warning = element.warnings.find(
273268
(w: Warning) => w.code === 'multiple-doc-comments');
274269
assert.isUndefined(warning);
275270
});
276271

277-
test('Elements with more than one doc comment have warning', async () => {
272+
test('Elements with more than one doc comment have warning', async() => {
278273
const element = await getElement('test-element-15.html');
279274
const warning = element.warnings.find(
280275
(w: Warning) => w.code === 'multiple-doc-comments')!;

src/test/polymer/polymer2-element-scanner-old-jsdoc_test.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -416,15 +416,13 @@ namespaced name.`,
416416
{
417417
name: 'customInstanceFunction',
418418
description: '',
419-
params: [],
420-
return: undefined
419+
params: [], return: undefined
421420
},
422421
{
423422
name: 'customInstanceFunctionWithJSDoc',
424423
description: 'This is the description for ' +
425424
'customInstanceFunctionWithJSDoc.',
426-
params: [],
427-
return: {
425+
params: [], return: {
428426
desc: 'The number 5, always.',
429427
type: 'Number',
430428
},
@@ -493,8 +491,7 @@ namespaced name.`,
493491
name: 'customInstanceFunctionWithParamsAndPrivateJSDoc',
494492
description: 'This is the description for\n' +
495493
'customInstanceFunctionWithParamsAndPrivateJSDoc.',
496-
params: [],
497-
return: undefined,
494+
params: [], return: undefined,
498495
},
499496
],
500497
warningUnderlines: [],

0 commit comments

Comments
 (0)