source: Dev/trunk/src/client/util/docscripts/lib/parser/DojoPackage.php

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 17.2 KB
Line 
1<?php
2
3require_once('Text.php');
4require_once('DojoFunctionCall.php');
5require_once('DojoExecutedFunction.php');
6
7class DojoPackage
8{
9  private $dojo;
10  protected $file; // The file reference (including dir) to the file;
11  private $code; // The source - comments
12  protected $source;
13  protected $declarations = array(); // Builds an array of functions declarations by name, with meta
14  protected $executions = array();
15  protected $calls = array(); // Builds an array of calls
16  protected $objects = array(); // Builds an array of objects
17  protected $aliases = array(); // Builds a key/value list of aliases in this file
18
19  public function __construct(Dojo $dojo, $file){
20    $this->dojo = $dojo;
21    $this->setFile($file);
22  }
23
24  public function destroy() {
25    array_walk($this->declarations, 'destroy_all');
26    unset($this->declarations);
27    array_walk($this->executions, 'destroy_all');
28    unset($this->executions);
29    array_walk($this->calls, 'destroy_all');
30    unset($this->calls);
31    array_walk($this->objects, 'destroy_all');
32    unset($this->objects);
33    array_walk($this->aliases, 'destroy_all');
34    unset($this->aliases);
35  }
36 
37  public function getFile(){
38    return $this->file;
39  }
40 
41  public function setFile($file){
42    $this->file = $file;
43  }
44
45  public function getFunctionDeclarations(){
46    $lines = $this->getCode();
47    $end = array(0, 0);
48
49    $matches = preg_grep('%function%', $lines);
50    $last_line = 0;
51    foreach($matches as $line_number => $line){
52      if($line_number < $last_line){
53        continue;
54      }
55
56      if(preg_match('%(\bfunction\s+[a-zA-Z0-9_.$]+\b\s*\(|\b[a-zA-Z_.$][\w.$]*(?:\.[a-zA-Z_.$][\w.$]|\["[^"]+"\])*\s*=\s*(new\s*)?function\s*\()%', $line, $match)) {
57        $declaration = new DojoFunctionDeclare($this);
58        $declaration->setStart($line_number, strpos($line, $match[0]));
59        $end = $declaration->build();
60        $last_line = $end[0];
61        $this->declarations[$declaration->getFunctionName()][] = $declaration;
62      }
63    }
64   
65    return $this->declarations;
66  }
67
68  /**
69   * Searches the file for the format: (function(){})();
70   */
71  public function getExecutedFunctions(){
72    if ($this->executions) {
73      return $this->executions;
74    }
75
76    $lines = $this->getCode();
77   
78    $matches = preg_grep('%function%', $lines);
79    $last_line = 0;
80    foreach ($matches as $line_number => $line) {
81      if ($line_number < $last_line) {
82        continue;
83      }
84
85      if (preg_match('%(?:([a-zA-Z_.$][\w.$]*)\s*=\s*)?(?:new\s*)?\(\s*function\s*\([^)]*\)\s*{%', $line, $match)) {
86        $execution = new DojoExecutedFunction($this);
87        $execution->setAnonymous(true);
88        if ($match[1]) {
89          $execution->setFunctionName($match[1]);
90        }
91        $execution->setStart($line_number, strpos($line, $match[0]));
92        $end = $execution->build();
93        if ($end) {
94          $last_line = $end[0];
95          $callee = $lines[$end[0]];
96          $this->executions[] = $execution;
97        }
98      }
99    }
100   
101    return $this->executions;
102  }
103 
104  /**
105   * Use this to find everywhere in the code a function is called.
106   *
107   * @param unknown_type $name
108   */
109  public function getFunctionCalls($name){
110    if ($this->calls[$name]) {
111      return $this->calls[$name];
112    }
113
114    $this->calls[$name] = array();
115    $lines = $this->getCode();
116    $lines = preg_grep('%\b' . preg_quote($name) . '\s*\(%', $lines);
117    foreach ($lines as $line_number => $line) {
118      $position = strpos($line, $name);
119      if ($line_number < $last_line_number || ($line_number == $last_line_number && $position < $last_position)) {
120        continue;
121      }
122      $call = new DojoFunctionCall($this, $line_number, $position);
123      list($last_line_number, $last_position) = $call->build();
124      $this->calls[$name][] = $call;
125    }
126    return $this->calls[$name];
127  }
128 
129  public function removeCodeFrom($lines){
130    $keys = array_keys($lines);
131    $first = array_shift($keys);
132    $last = array_pop($keys);
133    for($i = $first; $i <= $last; $i++) {
134      $line = $lines[$i];
135      if (preg_match('%function\s*\([^)]*\)\s*{%', $line, $match, PREG_OFFSET_CAPTURE)) {
136        $declaration = new DojoFunctionDeclare($this, $i, $match[0][1]);
137        list($i, ) = $declaration->build();
138        $lines = $declaration->removeCodeFrom($lines);
139      }
140      elseif (preg_match('%^.*(with|switch)\s*\([^(]*\)\s*{%', $line, $match)) {
141        $with_lines = Text::chop($lines, $i, strlen($match[0]) - 1, null, null, true);
142        list($end_line, $end_pos) = Text::findTermination($with_lines, '}', '{}()[]');
143        for ($j = $i; $j <= $end_line; $j++) {
144          $line = $lines[$j];
145          if ($j == $i) {
146            $lines[$j] = Text::blankOutAt($line, strlen($match[0]) - 1);
147          }
148          elseif ($j == $end_line) {
149            $lines[$j] = Text::blankOutAt($line, 0, $end_pos);
150          }
151          else {
152            $lines[$j] = Text::blankOut($line, $line);
153          }
154        }
155      }
156    }
157   
158    return $lines;
159  }
160 
161  public function getAliases(){
162    if($this->aliases){
163      return $this->aliases;
164    }
165
166    return $this->aliases;
167  }
168
169  public function getObjects(){
170    if ($this->objects) {
171      return $this->objects;
172    }
173   
174    $lines = $this->getCode();
175    foreach ($lines as $line_number => $line) {
176      if ($line_number < $end_line_number) {
177        continue;
178      }
179      if (preg_match('%\b([a-zA-Z0-9_.$]+)\s*=\s*{%', $line, $match, PREG_OFFSET_CAPTURE)) {
180        $object = new DojoObject($this, $line_number, $match[0][1] + strlen($match[0][0]) - 1);
181        $object->setName($match[1][0]);
182        list($end_line_number, $end_position) = $object->build();
183        $this->objects[] = $object;
184      }
185    }
186    return $this->objects;
187  }
188 
189  public function getSource(){
190    if ($this->source) {
191      return $this->source;
192    }
193    $lines = preg_split("%\r?\n%", file_get_contents($this->dojo->getDir() . $this->file));
194    $lines[] = '';
195    $in_comment = false;
196    foreach ($lines as $line_number => $line) {
197      $pos = 0;
198      $found = true;
199      while ($found) {
200        $found = false;
201        if (!$in_comment) {
202          if (preg_match('%/\*={5,}%', $line, $match, PREG_OFFSET_CAPTURE, $pos)) {
203            $line = $lines[$line_number] = Text::blankOut($match[0][0], $line);
204            $found = true;
205            $in_comment = true;
206            $pos = $match[0][1] + strlen($match[0][0]);
207          }
208        }
209        elseif (preg_match('%={5,}\*/%', $line, $match, PREG_OFFSET_CAPTURE, $pos)) {
210          $line = $lines[$line_number] = Text::blankOut($match[0][0], $line);
211          $found = true;
212          $in_comment = false;
213          $pos = $match[0][1] + strlen($match[0][0]);
214        }
215      }
216    }
217    return $this->source = $lines;
218  }
219 
220  /**
221   * Removes comments and strings, preserving layout
222   */
223  public function getCode(){
224    if ($this->code) {
225      return $this->code;
226    }
227   
228    $lines = $this->getSource();
229   
230    $in_comment = false;
231    foreach ($lines as $line_number => $line) {
232      //print "$line_number $line\n";
233      if ($in_comment !== false) {
234        if (preg_match('%^.*\*/%U', $line, $match)) {
235          $line = Text::blankOut($match[0], $line);
236          $in_comment = false;
237        }
238        else {
239          $line = Text::blankOut($line, $line);
240        }
241      }
242     
243      $position = 0;
244      $in_single_string = false;
245      $in_double_string = false;
246      $in_regex = false;
247
248      for ($i = 0; $i < 100; $i++) {
249        $matches = array();
250
251        if ($in_comment === false && $in_regex === false && $in_single_string === false && $in_double_string === false) {
252          // Match the start of a line, the word return or case, or a character in: =([{,|&;:?
253          // Followed by zero or more spaces
254          // Followed by a forward slash
255          // Not followed by another forward slash
256          if (preg_match('%(?:^|\breturn\b|[=([{,|&;:?])\s*/(?!/)%', $line, $match, PREG_OFFSET_CAPTURE, $position)) {
257            $matches[$match[0][1] + strlen($match[0][0]) - 1] = '/';
258          }
259          if (preg_match('%(?:^|\b(?:case|return)\b|[=([{,|&;:?+])\s*(["\'])%', $line, $match, PREG_OFFSET_CAPTURE, $position)) {
260            $matches[$match[0][1] + strlen($match[0][0]) - 1] = $match[1][0];
261          }
262          if (($pos = strpos($line, '//', $position)) !== false) {
263            $matches[$pos] = '//';
264          }
265          if (($pos = strpos($line, '/*', $position)) !== false) {
266            $matches[$pos] = '/*';
267          }
268        }
269        elseif ($in_regex !== false) {
270          // A / not preceeded by a / or \
271          // Followed by 0 or more spaces
272          // Followed by one of the characters: img.)]},|&;: or end of line
273          if (preg_match('%(?<![/\\\\])/\s*([img.)\]},|&;:]|$)%', $line, $match, PREG_OFFSET_CAPTURE, $position)) {
274            //print_r($match);
275            $matches[$match[0][1]] = '/';
276          }
277        }
278        elseif ($in_single_string !== false || $in_double_string !== false) {
279          for ($j = 0; $j < 999; $j++) {
280            $q_pos = strpos($line, ($in_single_string) ? "'" : '"', $position);
281
282            $bs_pos = strpos($line, '\\\\', $position);
283            $m_pos = strpos($line, '\\' . ($in_single_string) ? "'" : '"', $position);
284
285            if ($bs_pos !== false && $bs_pos < $q_pos) {
286              $position = $bs_pos + 2;
287            }
288            if ($m_pos !== false && $m_pos < $q_pos) {
289              $position = max($position, $m_post + 2);
290            }
291
292            if ($bs_pos === false && $m_pos === false) {
293              break;
294            }
295          }
296          $test = substr($line, $position);
297          if (preg_match('%(' . ($in_single_string ? "'" : '"') . ')\s*([+.)\]},|&;:?]|==|$)%', $line, $match, PREG_OFFSET_CAPTURE, $position)) {
298            $matches[$match[0][1]] = $match[1][0];
299          }
300        }
301        elseif ($in_comment !== false) {
302          if (($pos = strpos($line, '*/', $position)) !== false) {
303            $matches[$pos] = '*/';
304          }
305        }
306       
307        if (!$matches) {
308          break;
309        }
310       
311        ksort($matches);
312        foreach ($matches as $position => $match) {
313          if ($in_comment === false && $in_regex === false && $in_single_string === false && $in_double_string === false) {
314            if ($match == '"') {
315              $in_double_string = $position;
316              break;
317            }
318            elseif ($match == "'") {
319              $in_single_string = $position;
320              break;
321            }
322            elseif ($match == '/') {
323              $in_regex = $position;
324              break;
325            }
326            elseif ($match == '//') {
327              $line = Text::blankOutAt($line, $position);
328              break;
329            }
330            elseif ($match == '/*') {
331              $in_comment = $position;
332              ++$position;
333              break;
334            }
335          }
336          elseif ($in_double_string !== false && $match == '"') {
337            $line = Text::blankOutAt($line, $in_double_string + 1, $position - 1);
338            $in_double_string = false;
339          }
340          elseif ($in_single_string !== false && $match == "'") {
341            $line = Text::blankOutAt($line, $in_single_string + 1, $position - 1);
342            $in_single_string = false;
343          }
344          elseif ($in_regex !== false && $match == '/') {
345            $line = Text::blankOutAt($line, $in_regex + 1, $position - 1);
346            $in_regex = false;
347          }
348          elseif ($in_comment !== false && $match == '*/') {
349            $line = Text::blankOutAt($line, $in_comment + 2, $position - 1);
350            $in_comment = false;
351          }
352        }
353        ++$position;
354      }
355     
356      if($i == 500){
357        die("\$i should not reach 500: $line");
358      }
359     
360      if ($in_comment !== false && !empty($line)) {
361        $line = Text::blankOutAt($line, $in_comment);
362        $in_comment = 0;
363      }
364     
365      //print "$line_number $line\n";
366      $lines[$line_number] = $line;
367    }
368
369    return $this->code = $lines;
370  }
371
372  /**
373   * After all calls are done, return what's left
374   */
375  public function getExternalVariables(){
376    $lines = $this->getCode();
377
378    foreach ($this->objects as $pobject) {
379      foreach ($pobject->declarations as $declaration) {
380        $lines = Text::blankOutAtPositions($lines, $declaration->start[0], $declaration->start[1], $declaration->end[0], $declaration->end[1]);
381      }
382    }
383    foreach($this->declarations as $declarations){
384      foreach ($declarations as $declaration) {
385        $lines = Text::blankOutAtPositions($lines, $declaration->start[0], $declaration->start[1], $declaration->end[0], $declaration->end[1]);
386      }
387    }
388    foreach($this->calls as $call_name => $calls){
389      foreach($calls as $call){
390        $lines = Text::blankOutAtPositions($lines, $call->start[0], $call->start[1], $call->end[0], $call->end[1]);
391      }
392    }
393    foreach($this->executions as $execution){
394      $lines = Text::blankOutAtPositions($lines, $execution->start[0], $execution->start[1], $execution->end[0], $execution->end[1]);
395    }
396
397    $variables = array();
398    foreach (preg_grep('%=%', $lines) as $line_number => $line) {
399      if (preg_match('%\b([a-zA-Z_.$][\w.$]*)\s*=(?!=)\s*(function\s*\()?%', $line, $match)) {
400        $variables[] = $match[1];
401      }
402    }
403
404    return $variables;
405  }
406 
407  /**
408   * Remove items from the passed objects if they are inside of existing calls or declarations
409   */
410  public function removeSwallowed(&$objects){
411    $swallowed = array();
412    foreach ($objects as $i => $object) {
413      foreach ($this->objects as $pobject) {
414        if (($object->start[0] > $pobject->start[0] || ($object->start[0] == $pobject->start[0] && $object->start[1] > $pobject->start[1]))
415            && ($object->end[0] < $pobject->end[0] || ($object->end[0] == $pobject->end[0] && $object->end[1] < $pobject->end[1]))) {
416          if ($objects[$i]) {
417            $swallowed[] = $objects[$i];
418          }
419          unset($objects[$i]);
420        }       
421        foreach ($pobject->declarations as $declaration) {
422          if (($object->start[0] > $declaration->start[0] || ($object->start[0] == $declaration->start[0] && $object->start[1] > $declaration->start[1]))
423              && ($object->end[0] < $declaration->end[0] || ($object->end[0] == $declaration->end[0] && $object->end[1] < $declaration->end[1]))) {
424            if ($objects[$i]) {
425              $swallowed[] = $objects[$i];
426            }
427            unset($objects[$i]);
428          }
429        }
430      }
431      foreach($this->declarations as $declarations){
432        foreach ($declarations as $declaration) {
433          if(($object->start[0] > $declaration->start[0] || ($object->start[0] == $declaration->start[0] && $object->start[1] > $declaration->start[1]))
434              && ($object->end[0] < $declaration->end[0] || ($object->end[0] == $declaration->end[0] && $object->end[1] < $declaration->end[1]))) {
435            if ($objects[$i]) {
436              $swallowed[] = $objects[$i];
437            }
438            unset($objects[$i]);
439          }
440        }
441      }
442      foreach($this->calls as $call_name => $calls){
443        foreach($calls as $call){
444          if(($object->start[0] > $call->start[0] || ($object->start[0] == $call->start[0] && $object->start[1] > $call->start[1]))
445              && ($object->end[0] < $call->end[0] || ($object->end[0] == $call->end[0] && $object->end[1] < $call->end[1]))) {
446            if ($objects[$i]) {
447              $swallowed[] = $objects[$i];
448            }
449            unset($objects[$i]);
450          }
451        }
452      }
453      foreach($this->executions as $execution){
454        if(($object->start[0] > $execution->start[0] || ($object->start[0] == $execution->start[0] && $object->start[1] > $execution->start[1]))
455              && ($object->end[0] < $execution->end[0] || ($object->end[0] == $execution->end[0] && $object->end[1] < $execution->end[1]))) {
456            if ($objects[$i]) {
457              $swallowed[] = $objects[$i];
458            }
459            unset($objects[$i]);
460        }
461      }
462    }
463
464    return $swallowed;
465  }
466
467  public function getPackageName(){
468    $name = '';
469
470    if (!function_exists($this->dojo->namespace . '_package_name')) {
471      if (file_exists('modules/' . $this->dojo->namespace . '.module')){
472        include_once('modules/' . $this->dojo->namespace . '.module');
473      }
474      else {
475        $parts = explode('/', $this->file);
476        $file_parts = explode('.', array_pop($parts));
477        if (in_array('tests', $parts)) return;
478        array_pop($file_parts);
479        array_push($parts, implode('.', $file_parts));
480        array_unshift($parts, $this->dojo->namespace);
481        $name = implode('.', $parts);
482      }
483    }
484
485    if (function_exists($this->dojo->namespace . '_package_name')) {
486      $name = call_user_func($this->dojo->namespace . '_package_name', $this->dojo->namespace, $this->file);
487    }
488
489    if($name) return $name;
490    return 'null';
491  }
492
493  public function getResourceName(){
494    $name = '';
495
496    if (!function_exists($this->dojo->namespace . '_resource_name')) {
497      if (file_exists('modules/' . $this->dojo->namespace . '.module')) {
498        include_once('modules/' . $this->dojo->namespace . '.module');
499      }
500      else {
501        $name = $this->file;
502      }
503    }
504
505    if (function_exists($this->dojo->namespace . '_resource_name')) {
506      $name = call_user_func($this->dojo->namespace . '_resource_name', $this->dojo->namespace, $this->file);
507    }
508
509    if($name) return $name;
510    return 'null';
511  }
512
513}
514
515?>
Note: See TracBrowser for help on using the repository browser.