]> code.delx.au - gnu-emacs-elpa/blob - scopifier.js
Revert back to functions.
[gnu-emacs-elpa] / scopifier.js
1 'use strict';
2
3 var escope = require('escope'),
4 esprima = require('esprima'),
5
6 // Given an array of definitions, determines if a definition already exists
7 // for a given range. (escope detects variables twice if they are declared
8 // and initialized simultaneously; this filters them.)
9 isDefined = function (definitions, range) {
10 return definitions.some(function (definition) {
11 // Check for identical definitions.
12 return definition[1] === range[0] &&
13 definition[2] === range[1];
14 });
15 };
16
17 // Given code, returns an array of `[level, start, end]' tokens for
18 // context-coloring.
19 module.exports = function (code) {
20 var ast,
21 analyzedScopes,
22 scopes = [],
23 symbols = [],
24 comments,
25 emacsified;
26
27 // Gracefully handle parse errors by doing nothing.
28 try {
29 ast = esprima.parse(code, {
30 comment: true,
31 range: true
32 });
33 analyzedScopes = escope.analyze(ast).scopes;
34 } catch (error) {
35 process.exit(1);
36 }
37
38 analyzedScopes.forEach(function (scope) {
39 var definitions,
40 references;
41 if (scope.level !== undefined) {
42 // Having its level set implies it was already annotated.
43 return;
44 }
45 if (scope.upper) {
46 if (scope.upper.functionExpressionScope) {
47 // Pretend function expression scope doesn't exist.
48 scope.level = scope.upper.level;
49 scope.variables = scope.upper.variables.concat(scope.variables);
50 } else {
51 scope.level = scope.upper.level + 1;
52 }
53 } else {
54 // Base case.
55 scope.level = 0;
56 }
57 if (scope.functionExpressionScope) {
58 // We've only given the scope a level for posterity's sake. We're
59 // done now.
60 return;
61 }
62 scopes = scopes.concat([[
63 scope.level,
64 scope.block.range[0],
65 scope.block.range[1]
66 ]]);
67 definitions = scope.variables.reduce(function (definitions, variable) {
68 var mappedDefinitions = variable.defs.map(function (definition) {
69 var range = definition.name.range;
70 return [
71 scope.level,
72 range[0],
73 range[1]
74 ];
75 });
76 return definitions.concat(mappedDefinitions);
77 }, []);
78 references = scope.references.reduce(function (references, reference) {
79 var range = reference.identifier.range;
80 if (isDefined(definitions, range)) {
81 return references;
82 }
83 return references.concat([[
84 // Handle global references too.
85 reference.resolved ? reference.resolved.scope.level : 0,
86 range[0],
87 range[1]
88 ]]);
89 }, []);
90 symbols = symbols.concat(definitions).concat(references);
91 });
92
93 comments = ast.comments
94 .map(function (comment) {
95 var range = comment.range;
96 return [
97 -1,
98 range[0],
99 range[1]
100 ];
101 });
102
103 emacsified = scopes
104 .concat(symbols)
105 .concat(comments)
106 .map(function (token) {
107 // Emacs starts counting from 1.
108 return [
109 token[0],
110 token[1] + 1,
111 token[2] + 1
112 ];
113 });
114
115 return emacsified;
116 };