11'use strict' ;
22
3- var transformTools = require ( 'browserify-transform-tools' ) ;
3+ var merge = require ( 'lodash.merge' ) ;
4+
5+ var esprimaTransform = require ( './browserify-esprima-transform' ) ;
46
57/**
6- * A transform that supports explicit <code>@ngInject</code> annotations
8+ * Esprima based explicity @ngInject annotation with sourcemaps.
9+ * @param {object } opt An options hash
710 */
8- function ngInjectTransform ( ) {
9- return transformTools . makeFalafelTransform ( 'ngInjectTransform' , null , function transform ( node , opts , done ) {
10- if ( testAnnotated ( node ) ) {
11- var updater = falafelUpdater ( node ) ;
12-
13- // get an array of quoted parameter names
14- var parameters = node . params
15- . map ( function ( param ) {
16- return '"' + param . name + '"' ;
17- } ) ;
18-
19- // named function implies explicit $inject
20- // function baz(foo, bar){}; baz.$inject = [ "foo", "bar" ];
21- if ( node . id ) {
22- updater
23- . append ( [
24- ';' , // defensively insert semicolon as there is often problems otherwise
25- node . id . name + '.$inject = [' + parameters . join ( ',' ) + '];'
26- ] . join ( '\n' ) ) ;
27- }
28- // anonymous function implies array
29- // [ "foo", "bar", function(foo, bar) {} ]
30- else {
31- updater
32- . prepend ( '[' + parameters . concat ( '' ) . join ( ',' ) ) // note trailing comma
33- . append ( ']' ) ;
34- }
35- }
36- done ( ) ;
37- } ) ;
11+ function browserifyNginject ( opt ) {
12+ return esprimaTransform ( updater , opt ) ;
3813}
3914
40- function falafelUpdater ( node ) {
41- var self = {
42- prepend : prepend ,
43- append : append
44- } ;
45- return self ;
46-
47- function prepend ( value ) {
48-
49- // temporarily rewrite the range to be only the first character
50- var rangeHigh = node . range [ 1 ] ;
51- node . range [ 1 ] = node . range [ 0 ] + 1 ;
52- var firstChar = node . source ( ) ;
53-
54- // make all our changes within the source range of the first character
55- node . update ( value + firstChar ) ;
56-
57- // reinstate the range of the node
58- node . range [ 1 ] = rangeHigh ;
59-
60- // chainable
61- return self ;
62- }
63-
64- function append ( value ) {
15+ /**
16+ * The updater function for the esprima transform
17+ * @param {string } file The filename for the Browserify transform
18+ * @param {object } ast The esprima syntax tree
19+ * @returns {object } The transformed esprima syntax tree
20+ */
21+ function updater ( file , ast ) {
22+ ( ast . comments || [ ] )
23+ . filter ( function getEndLoc ( comment ) {
24+ return / @ n g I n j e c t / i. test ( comment . value ) ;
25+ } )
26+ . map ( getLocation ( 'end' , file ) )
27+ . map ( findClosestNode ( ast ) )
28+ . forEach ( processNode ( file ) ) ;
29+ return ast ;
30+ }
6531
66- // temporarily rewrite the range to be only the last character
67- var rangeLow = node . range [ 0 ] ;
68- node . range [ 0 ] = node . range [ 1 ] - 1 ;
69- var lastChar = node . source ( ) ;
32+ /**
33+ * Get a hash of the location source merged with the parameters of 'start' or 'end'.
34+ * @param {string } field Either 'start' or 'end'
35+ * @param {string } [source] An optional explicit source
36+ * @return {function } A method that retrieves the hash for a given node
37+ */
38+ function getLocation ( field , source ) {
39+ return function getLocationForNode ( node ) {
40+ return node && node . loc && merge ( { source : source || node . loc . source } , node . loc [ field ] ) ;
41+ } ;
42+ }
7043
71- // make all our changes within the source range of the last character
72- node . update ( lastChar + value ) ;
44+ /**
45+ * Find the AST node immediately following the given location.
46+ * @param {object } ast The esprima syntax tree
47+ * @returns {function } Method that finds the closes AST node for a given location
48+ */
49+ function findClosestNode ( ast ) {
50+ /**
51+ * @param {{line:number, column:number, source:string} } location A location to find relative to
52+ */
53+ return function forLocation ( location ) {
54+ var WHITE_LIST = / ^ (? ! i d | l o c | c o m m e n t s ) .* $ / ;
55+
56+ // use a queue to avoid recursion stack for large trees
57+ var queue = [ ast ] ;
58+
59+ // first find the node that immediately follows the location
60+ var result = null ;
61+ var closest = {
62+ line : Number . MAX_VALUE ,
63+ column : Number . MAX_VALUE
64+ } ;
65+
66+ // visit all nodes and find those closest following each comment
67+ // do not assume any node structure except that it has a 'loc' property
68+ while ( queue . length ) {
69+ var node = queue . shift ( ) ;
70+ for ( var key in node ) {
71+ var candidate = node [ key ] ;
72+ if ( candidate && ( typeof candidate === 'object' ) && WHITE_LIST . test ( key ) ) {
73+
74+ // find the closest
75+ var start = getLocation ( 'start' ) ( candidate ) ;
76+ var isCloser = isOrdered ( location , start , closest ) ;
77+ if ( isCloser ) {
78+ closest = start ;
79+ result = candidate ;
80+ }
81+
82+ // enqueue all objects
83+ queue . push ( candidate ) ;
84+ }
85+ }
86+ }
7387
74- // reinstate the range of the node
75- node . range [ 0 ] = rangeLow ;
88+ // error or complete
89+ if ( result ) {
90+ return result ;
91+ } else {
92+ throw new Error ( 'Cannot resolve annotation at line:' + location . line + ',col:' + location . column ) ;
93+ }
94+ } ;
7695
77- // chainable
78- return self ;
96+ /**
97+ * Test whether the given locations are ordered
98+ * @param {...{line:number, column:number, source:string} } Any number of locations
99+ * @returns True for all items being in order, else False
100+ */
101+ function isOrdered ( ) {
102+ return Array . prototype . slice . call ( arguments )
103+ . every ( function checkItemOrder ( current , i , array ) {
104+ var previous = array [ i - 1 ] || {
105+ line : 0 ,
106+ column : 0
107+ } ;
108+ return current && previous && ( ! current . source || ! previous . source || ( current . source === previous . source ) ) &&
109+ ( ( previous . line <= current . line ) ||
110+ ( ( previous . line === current . line ) && ( previous . column <= current . column ) ) ) ;
111+ } ) ;
79112 }
80113}
81114
82115/**
83- * Test whether the given node is annotated with <code>@ngInject</code> .
84- * @param {object } node The node to test
85- * @returns { boolean } True where annotated for ngInject else false
116+ * Add explicit dependency statements to the node .
117+ * @param {object } node The array in which the first item is the node to process
118+ * @param { object } [parent] An optional parent for the node
86119 */
87- function testAnnotated ( node ) {
88- var isFunction = / ^ F u n c t i o n ( D e c l a r a t i o n | E x p r e s s i o n ) $ / . test ( node . type ) ; // only functions should be annotated
89- if ( isFunction && node . parent ) {
90- // TODO @bholloway check up the parent chain while still the first body element and not a function
91- var split = node . parent . source ( ) . split ( node . source ( ) ) ;
92- var isAnnotated = ( split . length > 1 ) && / @ n g I n j e c t / . test ( split [ 0 ] ) ;
93- return isAnnotated ;
94- } else {
95- return false ;
96- }
120+ function processNode ( file ) {
121+ return function processNode ( node , parent ) {
122+ var values ;
123+ switch ( node . type ) {
124+ case 'ExpressionStatement' :
125+ values = [ ] . concat ( node . expression ) ;
126+ break ;
127+ case 'ReturnStatement' :
128+ values = [ ] . concat ( node . argument ) ;
129+ break ;
130+ //case 'VariableDeclaration':
131+ // values = node.declarations;
132+ // break;
133+ default :
134+ console . log ( node . type ) ;
135+ }
136+ } ;
97137}
98138
99- module . exports = ngInjectTransform ;
139+ // transformTools.makeFalafelTransform('ngInjectTransform', null, function transform(node, opts, done) {
140+ // if (testAnnotated(node)) {
141+ // var updater = falafelUpdater(node);
142+ //
143+ // // get an array of quoted parameter names
144+ // var parameters = node.params
145+ // .map(function(param) {
146+ // return '"' + param.name + '"';
147+ // });
148+ //
149+ // // named function implies explicit $inject
150+ // // function baz(foo, bar){}; baz.$inject = [ "foo", "bar" ];
151+ // if (node.id) {
152+ // updater
153+ // .append([
154+ // ';', // defensively insert semicolon as there is often problems otherwise
155+ // node.id.name + '.$inject = [' + parameters.join(',') + '];'
156+ // ].join('\n'));
157+ // }
158+ // // anonymous function implies array
159+ // // [ "foo", "bar", function(foo, bar) {} ]
160+ // else {
161+ // updater
162+ // .prepend('[' + parameters.concat('').join(',')) // note trailing comma
163+ // .append(']');
164+ // }
165+ // }
166+ // done();
167+ // });
168+ //
169+ //function falafelUpdater(node) {
170+ // var self = {
171+ // prepend: prepend,
172+ // append : append
173+ // };
174+ // return self;
175+ //
176+ // function prepend(value) {
177+ //
178+ // // temporarily rewrite the range to be only the first character
179+ // var rangeHigh = node.range[1];
180+ // node.range[1] = node.range[0] + 1;
181+ // var firstChar = node.source();
182+ //
183+ // // make all our changes within the source range of the first character
184+ // node.update(value + firstChar);
185+ //
186+ // // reinstate the range of the node
187+ // node.range[1] = rangeHigh;
188+ //
189+ // // chainable
190+ // return self;
191+ // }
192+ //
193+ // function append(value) {
194+ //
195+ // // temporarily rewrite the range to be only the last character
196+ // var rangeLow = node.range[0];
197+ // node.range[0] = node.range[1] - 1;
198+ // var lastChar = node.source();
199+ //
200+ // // make all our changes within the source range of the last character
201+ // node.update(lastChar + value);
202+ //
203+ // // reinstate the range of the node
204+ // node.range[0] = rangeLow;
205+ //
206+ // // chainable
207+ // return self;
208+ // }
209+ //}
210+ //
211+ ///**
212+ // * Test whether the given node is annotated with <code>@ngInject</code>.
213+ // * @param {object } node The node to test
214+ // * @returns {boolean } True where annotated for ngInject else false
215+ // */
216+ //function testAnnotated(node) {
217+ // var isFunction = /^Function(Declaration|Expression)$/.test(node.type); // only functions should be annotated
218+ // if (isFunction && node.parent) {
219+ //// TODO @bholloway check up the parent chain while still the first body element and not a function
220+ // var split = node.parent.source().split(node.source());
221+ // var isAnnotated = (split.length > 1) && /@ngInject/.test(split[0]);
222+ // return isAnnotated;
223+ // } else {
224+ // return false;
225+ // }
226+ //}
227+
228+ module . exports = browserifyNginject ;
0 commit comments