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

Commit 96d9624

Browse files
43081jrictic
authored andcommitted
Add a scanners for uses of elements in HTML (#354)
* initial element reference scanner * cleanup console refs and unify attribute interfaces * separate models out and add tests * add as default scanner & implement attributes * add tests for SourceRanges of element refs * tiny whitespace fix * check for attr existence before iterating * fix some whitespace linting * initialise warnings before use * create base element ref scanner * add license headers * source ranges should always exist * format code and add class comments
1 parent 86f59db commit 96d9624

5 files changed

+312
-3
lines changed

src/analyzer.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {BehaviorScanner} from './polymer/behavior-scanner';
3131
import {CssImportScanner} from './polymer/css-import-scanner';
3232
import {DomModuleScanner} from './polymer/dom-module-scanner';
3333
import {PolymerElementScanner} from './polymer/polymer-element-scanner';
34+
import {HtmlCustomElementReferenceScanner} from './html/html-element-reference-scanner';
3435
import {scan} from './scanning/scan';
3536
import {Scanner} from './scanning/scanner';
3637
import {UrlLoader} from './url-loader/url-loader';
@@ -96,7 +97,8 @@ export class Analyzer {
9697
'html',
9798
[
9899
new HtmlImportScanner(lazyEdges), new HtmlScriptScanner(),
99-
new HtmlStyleScanner(), new DomModuleScanner(), new CssImportScanner()
100+
new HtmlStyleScanner(), new DomModuleScanner(), new CssImportScanner(),
101+
new HtmlCustomElementReferenceScanner()
100102
]
101103
],
102104
[
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
* The complete set of authors may be found at
7+
* http://polymer.github.io/AUTHORS.txt
8+
* The complete set of contributors may be found at
9+
* http://polymer.github.io/CONTRIBUTORS.txt
10+
* Code distributed by Google as part of the polymer project is also
11+
* subject to an additional IP rights grant found at
12+
* http://polymer.github.io/PATENTS.txt
13+
*/
14+
15+
import * as dom5 from 'dom5';
16+
import {ASTNode} from 'parse5';
17+
import {ScannedElementReference} from '../model/element-reference';
18+
import {HtmlVisitor, ParsedHtmlDocument} from './html-document';
19+
import {HtmlScanner} from './html-scanner';
20+
21+
const isCustomElement = dom5.predicates.hasMatchingTagName(/(.+-)+.+/);
22+
23+
/**
24+
* Scans for HTML element references/uses in a given document.
25+
* All elements will be detected, including anything in <head>.
26+
* This scanner will not be loaded by default, but the custom
27+
* element extension of it will be.
28+
*/
29+
export class HtmlElementReferenceScanner implements HtmlScanner {
30+
matches(node: ASTNode): boolean {
31+
return !!node;
32+
}
33+
34+
async scan(
35+
document: ParsedHtmlDocument,
36+
visit: (visitor: HtmlVisitor) => Promise<void>):
37+
Promise<ScannedElementReference[]> {
38+
let elements: ScannedElementReference[] = [];
39+
40+
await visit((node) => {
41+
if (node.tagName && this.matches(node)) {
42+
const element = new ScannedElementReference(
43+
node.tagName, document.sourceRangeForNode(node)!, node);
44+
45+
if (node.attrs) {
46+
for (const attr of node.attrs) {
47+
element.attributes.push({
48+
name: attr.name,
49+
value: attr.value,
50+
sourceRange: document.sourceRangeForAttribute(node, attr.name)!,
51+
nameSourceRange:
52+
document.sourceRangeForAttributeName(node, attr.name)!,
53+
valueSourceRange:
54+
document.sourceRangeForAttributeValue(node, attr.name)
55+
});
56+
}
57+
}
58+
59+
elements.push(element);
60+
}
61+
});
62+
63+
return elements;
64+
}
65+
}
66+
67+
/**
68+
* Scans for custom element references/uses.
69+
* All custom elements will be detected except <dom-module>.
70+
* This is a default scanner.
71+
*/
72+
export class HtmlCustomElementReferenceScanner extends
73+
HtmlElementReferenceScanner {
74+
matches(node: ASTNode): boolean {
75+
return isCustomElement(node) && node.nodeName !== 'dom-module';
76+
}
77+
}

src/model/element-reference.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
* The complete set of authors may be found at
7+
* http://polymer.github.io/AUTHORS.txt
8+
* The complete set of contributors may be found at
9+
* http://polymer.github.io/CONTRIBUTORS.txt
10+
* Code distributed by Google as part of the polymer project is also
11+
* subject to an additional IP rights grant found at
12+
* http://polymer.github.io/PATENTS.txt
13+
*/
14+
15+
import * as dom5 from 'dom5';
16+
17+
import {Feature, Resolvable, SourceRange} from '../model/model';
18+
import {Warning} from '../warning/warning';
19+
20+
export interface Attribute {
21+
name: string;
22+
sourceRange: SourceRange;
23+
nameSourceRange: SourceRange;
24+
valueSourceRange: SourceRange|undefined;
25+
value?: string;
26+
}
27+
28+
export class ElementReference implements Feature {
29+
tagName: string;
30+
attributes: Attribute[] = [];
31+
sourceRange: SourceRange;
32+
astNode: dom5.Node;
33+
warnings: Warning[] = [];
34+
kinds: Set<string> = new Set(['element-reference']);
35+
36+
get identifiers(): Set<string> {
37+
return new Set([this.tagName]);
38+
}
39+
}
40+
41+
export class ScannedElementReference implements Resolvable {
42+
tagName: string;
43+
attributes: Attribute[] = [];
44+
sourceRange: SourceRange;
45+
astNode: dom5.Node;
46+
warnings: Warning[] = [];
47+
48+
constructor(tagName: string, sourceRange: SourceRange, ast: dom5.Node) {
49+
this.tagName = tagName;
50+
this.sourceRange = sourceRange;
51+
this.astNode = ast;
52+
}
53+
54+
resolve(): ElementReference {
55+
const ref = new ElementReference();
56+
Object.assign(ref, this);
57+
return ref;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
* The complete set of authors may be found at
7+
* http://polymer.github.io/AUTHORS.txt
8+
* The complete set of contributors may be found at
9+
* http://polymer.github.io/CONTRIBUTORS.txt
10+
* Code distributed by Google as part of the polymer project is also
11+
* subject to an additional IP rights grant found at
12+
* http://polymer.github.io/PATENTS.txt
13+
*/
14+
15+
import {assert} from 'chai';
16+
17+
import {Analyzer} from '../../analyzer';
18+
import {HtmlVisitor} from '../../html/html-document';
19+
import {HtmlCustomElementReferenceScanner, HtmlElementReferenceScanner} from '../../html/html-element-reference-scanner';
20+
import {HtmlParser} from '../../html/html-parser';
21+
import {SourceRange} from '../../model/model';
22+
import {WarningPrinter} from '../../warning/warning-printer';
23+
24+
suite('HtmlElementReferenceScanner', () => {
25+
26+
suite('scan()', () => {
27+
let scanner: HtmlElementReferenceScanner;
28+
29+
setup(() => {
30+
scanner = new HtmlElementReferenceScanner();
31+
});
32+
33+
test('finds element references', async() => {
34+
const contents = `<html><head></head>
35+
<body>
36+
<div>Foo</div>
37+
<x-foo></x-foo>
38+
<div>
39+
<x-bar></x-bar>
40+
</div>
41+
</body></html>`;
42+
43+
const document = new HtmlParser().parse(contents, 'test-document.html');
44+
let visit = async(visitor: HtmlVisitor) => document.visit([visitor]);
45+
46+
const features = await scanner.scan(document, visit);
47+
48+
assert.deepEqual(
49+
features.map(f => f.tagName),
50+
['html', 'head', 'body', 'div', 'x-foo', 'div', 'x-bar']);
51+
});
52+
});
53+
});
54+
55+
suite('HtmlCustomElementReferenceScanner', () => {
56+
57+
suite('scan()', () => {
58+
let scanner: HtmlCustomElementReferenceScanner;
59+
let contents = '';
60+
const loader = {canLoad: () => true, load: () => Promise.resolve(contents)};
61+
const warningPrinter = new WarningPrinter(
62+
null as any, {analyzer: new Analyzer({urlLoader: loader})});
63+
64+
async function getUnderlinedText(sourceRange: SourceRange|undefined) {
65+
if (!sourceRange) {
66+
return 'No source range produced';
67+
}
68+
return '\n' + await warningPrinter.getUnderlinedText(sourceRange);
69+
}
70+
71+
setup(() => {
72+
scanner = new HtmlCustomElementReferenceScanner();
73+
});
74+
75+
test('finds custom element references', async() => {
76+
contents = `<html><body>
77+
<div>Foo</div>
78+
<x-foo a=5 b="test" c></x-foo>
79+
<div>
80+
<x-bar></x-bar>
81+
</div>
82+
<h1>Bar</h1>
83+
</body></html>`;
84+
85+
const document = new HtmlParser().parse(contents, 'test-document.html');
86+
let visit = async(visitor: HtmlVisitor) => document.visit([visitor]);
87+
88+
const features = await scanner.scan(document, visit);
89+
90+
assert.deepEqual(features.map(f => f.tagName), ['x-foo', 'x-bar']);
91+
92+
assert.deepEqual(
93+
features[0].attributes.map(a => [a.name, a.value]),
94+
[['a', '5'], ['b', 'test'], ['c', '']]);
95+
96+
const sourceRanges = await Promise.all(
97+
features.map(async f => await getUnderlinedText(f.sourceRange)));
98+
99+
assert.deepEqual(sourceRanges, [
100+
`
101+
<x-foo a=5 b="test" c></x-foo>
102+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~`,
103+
`
104+
<x-bar></x-bar>
105+
~~~~~~~~~~~~~~~`
106+
]);
107+
108+
const attrRanges = await Promise.all(features.map(
109+
async f => await Promise.all(f.attributes.map(
110+
async a => await getUnderlinedText(a.sourceRange)))));
111+
112+
assert.deepEqual(attrRanges, [
113+
[
114+
`
115+
<x-foo a=5 b="test" c></x-foo>
116+
~~~`,
117+
`
118+
<x-foo a=5 b="test" c></x-foo>
119+
~~~~~~~~`,
120+
`
121+
<x-foo a=5 b="test" c></x-foo>
122+
~`
123+
],
124+
[]
125+
]);
126+
127+
const attrNameRanges = await Promise.all(features.map(
128+
async f => await Promise.all(f.attributes.map(
129+
async a => await getUnderlinedText(a.nameSourceRange)))));
130+
131+
assert.deepEqual(attrNameRanges, [
132+
[
133+
`
134+
<x-foo a=5 b="test" c></x-foo>
135+
~`,
136+
`
137+
<x-foo a=5 b="test" c></x-foo>
138+
~`,
139+
`
140+
<x-foo a=5 b="test" c></x-foo>
141+
~`
142+
],
143+
[]
144+
]);
145+
146+
const attrValueRanges = await Promise.all(features.map(
147+
async f => await Promise.all(f.attributes.map(
148+
async a => await getUnderlinedText(a.valueSourceRange)))));
149+
150+
assert.deepEqual(attrValueRanges, [
151+
[
152+
`
153+
<x-foo a=5 b="test" c></x-foo>
154+
~`,
155+
`
156+
<x-foo a=5 b="test" c></x-foo>
157+
~~~~~~`,
158+
`No source range produced`
159+
],
160+
[]
161+
]);
162+
});
163+
164+
});
165+
166+
});

src/test/html/html-import-scanner_test.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,13 @@ suite('HtmlImportScanner', () => {
8282
const visit = async(visitor: HtmlVisitor) => document.visit([visitor]);
8383

8484
const features = await scanner.scan(document, visit);
85-
assert.deepEqual(features.map(f => f.type), ['html-import', 'lazy-html-import', 'lazy-html-import', 'lazy-html-import']);
86-
assert.deepEqual(features.map(f => f.url), ['polymer.html', 'lazy1.html', 'lazy2.html', 'lazy3.html']);
85+
assert.deepEqual(features.map(f => f.type), [
86+
'html-import', 'lazy-html-import', 'lazy-html-import',
87+
'lazy-html-import'
88+
]);
89+
assert.deepEqual(
90+
features.map(f => f.url),
91+
['polymer.html', 'lazy1.html', 'lazy2.html', 'lazy3.html']);
8792
});
8893

8994
});

0 commit comments

Comments
 (0)