Skip to content

Commit 2c87e8d

Browse files
committed
New: external reference analysis
1 parent 560028f commit 2c87e8d

22 files changed

+624
-262
lines changed

docs/ast.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,18 @@ interface VExpressionContainer <: Node {
5757
type: "VExpressionContainer"
5858
expression: Expression | null
5959
syntaxError: Error | null
60+
references: [ Reference ]
61+
}
62+
63+
interface Reference {
64+
id: Identifier
65+
mode: "rw" | "r" | "w"
6066
}
6167
```
6268

6369
- This is mustaches or directive values.
6470
- If syntax errors exist, `expression` is `null` and `syntaxError` is an error object. Otherwise, `expression` is an [Expression] node and `syntaxError` is `null`.
71+
- `Reference` is objects but not `Node`. Those are external references which are in the expression.
6572

6673
## VDirectiveKey
6774

@@ -138,10 +145,16 @@ interface VElement <: Node {
138145
startTag: VStartTag
139146
children: [ VText | VExpressionContainer | VElement ]
140147
endTag: VEndTag | null
148+
variables: [ Variable ]
149+
}
150+
151+
interface Variable {
152+
id: Identifier
141153
}
142154
```
143155

144-
If `startTag.selfClosing` is `false` and `endTag` is `null`, the element does not have their end tag. E.g. `<li>Foo.`.
156+
- If `startTag.selfClosing` is `false` and `endTag` is `null`, the element does not have their end tag. E.g. `<li>Foo.`.
157+
- `Variable` is objects but not `Node`. Those are variable declarations that child elements can use. The elements which have [`v-for` directives] or a special attribute [scope] can declare variables.
145158

146159
## Program
147160

@@ -161,3 +174,6 @@ This supports only HTML for now. However, I'm going to add other languages Vue.j
161174
[Literal]: https://github.com/estree/estree/blob/master/es5.md#literal
162175
[Pattern]: https://github.com/estree/estree/blob/master/es5.md#patterns
163176
[Identifier]: https://github.com/estree/estree/blob/master/es5.md#identifier
177+
178+
[`v-for` directives]: https://vuejs.org/v2/guide/list.html#v-for
179+
[scope]: https://vuejs.org/v2/guide/components.html#Scoped-Slots

lib/analyze-references.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @author Toru Nagashima <https://github.com/mysticatea>
3+
* @copyright 2017 Toru Nagashima. All rights reserved.
4+
* See LICENSE file in root directory for full license.
5+
*/
6+
"use strict"
7+
8+
//------------------------------------------------------------------------------
9+
// Requirements
10+
//------------------------------------------------------------------------------
11+
12+
const ScopeAnalyzer = require("eslint-scope")
13+
14+
//------------------------------------------------------------------------------
15+
// Helpers
16+
//------------------------------------------------------------------------------
17+
18+
const OPTS = {ignoreEval: true, ecmaVersion: 8}
19+
20+
/**
21+
* Analyze the external references of the given AST.
22+
* @param {ASTNode} ast The root node to analyze.
23+
* @returns {Reference[]} The reference objects of external references.
24+
*/
25+
function analyzeReferences(ast) {
26+
const result = ScopeAnalyzer.analyze(ast, OPTS)
27+
const scope = result.acquire(ast)
28+
const references = scope.through
29+
30+
return references.map(r => ({
31+
id: r.identifier,
32+
mode: (
33+
r.isReadOnly() ? "r" :
34+
r.isWriteOnly() ? "w" :
35+
/* otherwise */ "rw"
36+
),
37+
}))
38+
}
39+
40+
//------------------------------------------------------------------------------
41+
// Exports
42+
//------------------------------------------------------------------------------
43+
44+
module.exports = analyzeReferences

lib/script-parser.js

+24-13
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//------------------------------------------------------------------------------
1111

1212
const sortedIndexBy = require("lodash.sortedindexby")
13+
const analyzeReferences = require("./analyze-references")
1314
const decodeHtmlEntities = require("./decode-html-entities")
1415
const traverseNodes = require("./traverse-nodes")
1516

@@ -66,13 +67,15 @@ function fixRangeAndLocByGap(node, tokenGenerator, gaps, codeStart) {
6667
* 1. Set `node.parent`.
6768
* 2. Fix `node.range` and `node.loc` for HTML entities.
6869
*
69-
* @param {ASTNode} ast The AST root node to initialize.
70+
* @param {ASTNode} ast The AST root node to modify.
71+
* @param {ASTNode[]} tokens The tokens to modify.
72+
* @param {ASTNode[]} comments The comments to modify.
7073
* @param {TokenGenerator} tokenGenerator The token generator to calculate locations.
7174
* @param {(number[])[]} gaps The gaps to re-calculate locations.
7275
* @param {number} codeStart The start offset of the expression.
7376
* @returns {void}
7477
*/
75-
function postprocess(ast, tokenGenerator, gaps, codeStart) {
78+
function postprocess(ast, tokens, comments, tokenGenerator, gaps, codeStart) {
7679
const gapsExist = gaps.length >= 1
7780

7881
traverseNodes(ast, {
@@ -88,10 +91,10 @@ function postprocess(ast, tokenGenerator, gaps, codeStart) {
8891
})
8992

9093
if (gapsExist) {
91-
for (const token of ast.tokens) {
94+
for (const token of tokens) {
9295
fixRangeAndLocByGap(token, tokenGenerator, gaps, codeStart)
9396
}
94-
for (const comment of ast.comments) {
97+
for (const comment of comments) {
9598
fixRangeAndLocByGap(comment, tokenGenerator, gaps, codeStart)
9699
}
97100
}
@@ -176,7 +179,7 @@ class ScriptParser {
176179
* @param {number} start The start offset to parse.
177180
* @param {number} end The end offset to parse.
178181
* @param {TokenGenerator} tokenGenerator The token generator to fix loc.
179-
* @returns {ASTNode} The created AST node.
182+
* @returns {{expression:ASTNode, tokens:ASTNode[], comments:ASTNode[], references:Reference[]}} The result of parsing.
180183
*/
181184
parseExpression(start, end, tokenGenerator) {
182185
const codeStart = this.getInlineScriptStart(start)
@@ -207,15 +210,23 @@ class ScriptParser {
207210
)
208211
}
209212

213+
const references = analyzeReferences(ast)
210214
const expression = ast.body[0].expression
211-
expression.tokens = ast.tokens || []
212-
expression.comments = ast.comments || []
213-
expression.tokens.shift()
214-
expression.tokens.pop()
215-
216-
postprocess(expression, tokenGenerator, gaps, codeStart)
217-
218-
return expression
215+
const tokens = ast.tokens || []
216+
const comments = ast.comments || []
217+
tokens.shift()
218+
tokens.pop()
219+
220+
postprocess(
221+
expression,
222+
tokens,
223+
comments,
224+
tokenGenerator,
225+
gaps,
226+
codeStart
227+
)
228+
229+
return {expression, tokens, comments, references}
219230
}
220231
}
221232

0 commit comments

Comments
 (0)