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