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

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

Added Dojo 1.9.3 release.

File size: 14.9 KB
Line 
1<?php
2
3require_once('DojoFunctionBody.php');
4require_once('DojoBlock.php');
5require_once('DojoParameters.php');
6
7class DojoFunctionDeclare extends DojoBlock
8{
9  private $object = 'DojoFunctionDeclare';
10
11  private $parameters;
12  private $function_name;
13  protected $body;
14
15  private $in_executed_function = null;
16  private $anonymous = false;
17  private $prototype = '';
18  private $constructor = false;
19  private $aliases = '';
20
21  public function __construct($package, $line_number = false, $position = false){
22    parent::__construct($package, $line_number, $position);
23    $this->parameters = new DojoParameters($package);
24    $this->body = new DojoFunctionBody($package);
25  }
26
27  public static function parseVariable(&$comment) {
28    $summary = $comment;
29    $tags = array();
30    if (preg_match('%^\s*([a-z\s]+)\]\s*%', $summary, $match)) {
31      $tags = preg_split('%\s+%', $match[1]);
32      $summary = $comment = substr($summary, strlen($match[0]));
33    }
34
35    list($type, $summary) = preg_split('%\s%', $summary, 2);
36    $type = preg_replace('%(^[^a-zA-Z0-9._$]|[^a-zA-Z0-9._$?]$)%', '', $type);
37
38    $options = array();
39    if(!empty($type)){
40      if(strpos($type, '?')){
41        $type = substr($type, 0, strlen($type) - 1);
42        $options['optional'] = true;
43      }
44      if(strpos($type, '...')){
45        $type = substr($type, 0, strlen($type) - 3);
46        $options['repeating'] = true;
47      }
48    }
49
50    return array($tags, $type, $options, $summary);
51  }
52
53  public function destroy() {
54    if (!$this->destroyed) {
55      $this->destroyed = true;
56      $this->parameters->destroy();
57      unset($this->parameters);
58      $this->body->destroy();
59      unset($this->body);
60      if ($this->in_executed_function) {
61        $this->in_executed_function->destroy();
62      }
63      unset($this->in_executed_function);
64    }
65  }
66
67  public function getFunctionName(){
68    return $this->function_name;
69  }
70
71  public function getAliases(){
72    return $this->aliases;
73  }
74
75  public function rebuildAliases($map) {
76    if (is_array($this->aliases)) {
77      foreach ($this->aliases as $i => $alias) {
78        foreach ($map as $internal_name => $external_name) {
79          if (strpos($alias, $internal_name . '.') === 0) {
80            if (!$external_name) continue 2;
81            $alias = $external_name . substr($alias, strlen($internal_name));
82          }
83        }
84        $this->aliases[$i] = $alias;
85      }
86    }
87  }
88
89  public function setFunctionName($function_name){
90    $this->function_name = $function_name;
91  }
92
93  public function setPrototype($function_name){
94    $this->prototype = $function_name;
95  }
96 
97  public function getPrototype(){
98    return $this->prototype;
99  }
100 
101  public function setInstance($function_name){
102    $this->instance = $function_name;
103  }
104 
105  public function getInstance(){
106    return $this->instance;
107  }
108 
109  public function setConstructor($constructor){
110    $this->constructor = $constructor;
111  }
112 
113  public function isConstructor(){
114    return $this->constructor;
115  }
116
117  public function setAnonymous($anonymous) {
118    $this->anonymous = $anonymous;
119  }
120
121  public function isAnonymous(){
122    return $this->anonymous;
123  }
124 
125  public function isThis(){
126    return ($this->prototype || $this->instance);
127  }
128 
129  public function getThis(){
130    return ($this->prototype) ? $this->prototype : $this->instance;
131  }
132
133  public function setExecutedFunction($function) {
134    $this->in_executed_function = $function;
135  }
136 
137  public function getInstanceVariableNames(){
138    return array_unique($this->body->getInstanceVariableNames());
139  }
140
141  public function removeSwallowedMixins(&$mixins) {
142    return $this->body->removeSwallowedMixins($mixins);
143  }
144 
145  public function getReturnComments(){
146    return array_unique($this->body->getReturnComments());
147  }
148 
149  public function getThisInheritanceCalls(){
150    $output = array();
151    $calls = array_unique($this->body->getThisInheritanceCalls());
152
153    if ($this->in_executed_function) {
154      $internalized = $this->in_executed_function->getLocalVariableNames();
155    }
156
157    $parameters = $this->getParameterNames();
158    foreach ($calls as $call) {
159      if (!in_array($call, $parameters)) {
160        if ($internalized) {
161          foreach (array_keys($internalized) as $variable) {
162            if (strpos($call, $variable . '.') === 0) {
163              continue 2;
164            }
165          }
166        }
167        $output[] = $call;
168      }
169    }
170
171    return $output;
172  }
173
174  public function getVariableNames($function_name, $parameter_names=array()){
175    return $this->body->getExternalizedVariableNames($function_name, $parameter_names);
176  }
177
178  public function getFunctionDeclarations(){
179    return $this->body->getExternalizedFunctionDeclarations();
180  }
181
182  public function getObjects(){
183    return $this->body->getExternalizedObjects(false, $this->getParameterNames());
184  }
185
186  public function getLocalVariableNames(){
187    return $this->body->getLocalVariableNames();
188  }
189 
190  public function removeCodeFrom($lines){
191    $this->build();
192
193    return Text::blankOutAtPositions($lines, $this->start[0], $this->start[1], $this->end[0], $this->end[1]);
194  }
195 
196  public function build(){
197    if (!$this->start) {
198      die("DojoFunctionDeclare->build() used before setting a start position");
199    }
200    if($this->end){
201      return $this->end;
202    }
203 
204    $lines = Text::chop($this->package->getCode(), $this->start[0], $this->start[1]);
205    $line = trim($lines[$this->start[0]]);
206    if(strpos($line, 'function') === 0){
207      $line = substr($line, 8);
208      preg_match('%[^\s]%', $line, $match);
209      if($match[0] != '('){
210          $this->function_name = trim(substr($line, 0, strpos($line, '(')));
211      }
212    }else{
213      $name = trim(substr($line, 0, strpos($line, '=')));
214      $extra = substr($line, strpos($line, '=') + 1);
215      if(preg_match('%^\s+new\s+%', $name, $match) || preg_match('%^\s*new\s+%', $extra, $match)){
216        $this->anonymous = true;
217        $name = str_replace($match[0], '', $name);
218      }
219      if(($pos = strpos($name, '.prototype.')) !== false){
220        $this->prototype = substr($name, 0, $pos);
221        $name = str_replace('.prototype', '', $name);
222      }
223      if(($pos = strpos($name, 'this.')) === 0){
224        $this->instance = $this->getFunctionName();
225        $name = $this->getFunctionName() . "." . preg_replace('%^this\.%', '', $name);
226      }
227
228      if (!$this->isAnonymous()) {
229        $full_lines = Text::chop($this->package->getCode(), $this->start[0], 0);
230        $full_line = substr($full_lines[$this->start[0]], 0, $this->start[1]);
231        if (preg_match('%(?:[a-zA-Z0-9._$]+\s*=\s*)+$%', $full_line, $matches)) {
232          $aliases = preg_split('%\s*=\s*%', $matches[0]);
233          foreach ($aliases as $alias) {
234            $alias = trim($alias);
235            if ($alias) {
236              if (strpos($alias, 'this.') === 0) {
237                $alias = $this->getFunctionName() . "." . preg_replace('%^this\.%', '', $alias);
238              }
239              $this->aliases[] = $alias;
240            }
241          }
242        }
243      }
244
245      if (strpos($name, '[') !== false) {
246        $source_lines = Text::chop($this->package->getSource(), $this->start[0], $this->start[1]);
247        $source_line = trim($source_lines[$this->start[0]]);
248        preg_match('%^\s*([a-zA-Z_.$][\w.$]*(?:\.[a-zA-Z_.$][\w.$]|\["[^"]+"\])*)\s*=\s*function%', $source_line, $match);
249        $name = preg_replace('%\["([^"]+)"\]%', '.$1', $match[1]);
250      }
251      $this->function_name = $name;
252    }
253   
254    $this->parameters->setStart($this->start[0], strpos($lines[$this->start[0]], '('));
255    $end = $this->parameters->build();
256   
257    $lines = Text::chop($this->package->getCode(), $end[0], $end[1]);
258    foreach($lines as $line_number => $line){
259      if(($pos = strpos($line, '{')) !== false){
260        $this->body->setStart($line_number, $pos);
261        return $this->end = $this->body->build();
262      }
263    }
264  }
265 
266  public function getParameter($pos){
267    return $this->parameters->getParameter($pos);
268  }
269 
270  public function getParameters(){
271    return $this->parameters->getParameters();
272  }
273
274  public function getParameterNames(){
275    $names = array();
276    $parameters = $this->getParameters();
277    foreach ($parameters as $parameter) {
278      if($parameter->isA(DojoVariable)){
279        $names[] = $parameter->getVariable();
280      }
281    }
282    return $names;
283  }
284 
285  public function addBlockCommentKey($key){
286    $this->body->addBlockCommentKey($key);
287  }
288
289  public function addBlockCommentKeySet($key){
290    $this->body->addBlockCommentKeySet($key);
291  }
292
293  public function getBlockCommentKeys(){
294    return $this->body->getBlockCommentKeys();
295  }
296 
297  public function getBlockComment($key){
298    return $this->body->getBlockComment($key);
299  }
300 
301  public function getSource(){
302    return $this->body->getSource();
303  }
304 
305  public function getInstanceFunctions($function_name){
306    return $this->body->getInstanceFunctions($function_name);
307  }
308 
309  public function rollOut(&$output){
310    // Basically, any this.variables in here never impact this object, they apply to the "this" function
311    $masquerading_as_function = $function_name = $this->getFunctionName();
312    if (substr($masquerading_as_function, 0, 7) == 'window.'){
313      $masquerading_as_function = $function_name = substr($masquerading_as_function, 7);
314    }
315    $check_keys = array('summary','description','returns','tags','exceptions');
316
317    if (!empty($output[$function_name]['aliases'])) {
318      unset($output[$function_name]['aliases']); // This is implemented, it aliases nothing.
319    }
320
321    if ($this->isThis()) {
322      $masquerading_as_function = $this->getThis();
323    }
324
325    $output[$function_name]['type'] = 'Function';
326    if (!empty($output[$masquerading_as_function])) {
327      $output[$masquerading_as_function]['type'] = 'Function';
328    }
329
330    if ($aliases = $this->getAliases()) {
331      foreach ($aliases as $alias) {
332        if (empty($output[$alias])) {
333          $output[$alias]['aliases'] = $function_name;
334        }
335      }
336    }
337
338    $parameters = $this->getParameters();
339    foreach ($parameters as $parameter) {
340      if($parameter->isA(DojoVariable)){
341        $parameter_name = $parameter->getVariable();
342        $parameter_type = $parameter->getType();
343        if(strpos($parameter_type, '?')){
344          $parameter_type = substr($parameter_type, 0, strlen($parameter_type) - 1);
345          $output[$function_name]['parameters'][$parameter_name]['optional'] = true;
346        }
347        if(strpos($parameter_type, '...')){
348          $parameter_type = substr($parameter_type, 0, strlen($parameter_type) - 3);
349          $output[$function_name]['parameters'][$parameter_name]['repeating'] = true;
350        }
351        if (empty($output[$function_name]['parameters'][$parameter_name]['type']) || $parameter_type) {
352          $output[$function_name]['parameters'][$parameter_name]['type'] = $parameter_type;
353        }
354
355        $this->addBlockCommentKey($parameter->getVariable());
356      }
357    }
358
359    if ($this->isAnonymous()) {
360      $output[$function_name]['initialized'] = true;
361
362      $declarations = $this->body->getExternalizedFunctionDeclarations($function_name);
363      foreach ($declarations as $declaration) {
364        $declaration->rollout($output);
365      }
366
367      $variables = $this->body->getExternalizedInstanceVariableNames($function_name, $this->getParameterNames());
368      foreach($variables as $variable) {
369        $output[$function_name . '.' . $variable]['instance'] = $function_name;
370      }
371
372      $variables = $this->body->getExternalizedVariableNames($function_name, $this->getParameterNames());
373      foreach($variables as $variable) {
374        list($first,) = explode('.', $variable, 2);
375        if (!is_array($output[$function_name]['parameters']) || !array_key_exists($first, $output[$function_name]['parameters'])) {
376          if (empty($output[$variable])) {
377            $output[$variable] = array();
378          }
379        }
380      }
381    }
382
383    foreach($check_keys as $ck){
384      $this->addBlockCommentKey($ck);
385    }
386    $this->addBlockCommentKeySet('example');
387    $check_keys[] = 'example';
388
389    $output[$function_name]['source'] = $this->getSource();
390
391    $all_variables = array();
392    $instance_variables = $this->getInstanceVariableNames();
393    foreach($instance_variables as $instance_variable){
394      $this->addBlockCommentKey($instance_variable);
395      $all_variables[] = $instance_variable;
396
397      $full_variable_name = "{$masquerading_as_function}.{$instance_variable}";
398      $output[$full_variable_name]['instance'] = $masquerading_as_function;
399    }
400
401    $instance_functions = $this->getInstanceFunctions($function_name);
402    foreach($instance_functions as $instance_function){
403      $instance_function->rollOut($output);
404      $output[$instance_function->getFunctionName()]['instance'] = $function_name;
405    }
406
407    $comment_keys = $this->getBlockCommentKeys();
408    foreach($comment_keys as $key){
409      if ($key == 'returns') {
410        $output[$function_name]['return_summary'] = $this->getBlockComment($key);
411      }
412      elseif (in_array($key, $check_keys)) {
413        $output[$function_name][$key] = $this->getBlockComment($key);
414      }
415      if (in_array($key, $all_variables) && $comment = $this->getBlockComment($key)) {
416         list($type, $comment) = preg_split('%\s+%', $comment, 2);
417        $type = preg_replace('%(^[^a-zA-Z0-9._$]|[^a-zA-Z0-9._$?]$)%', '', $type);
418        if($type){
419          $output[$function_name . '.' . $key]['type'] = $type;
420        }
421        $output[$function_name . '.' . $key]['summary'] = $comment;
422      }
423      if (!empty($output[$function_name]['parameters']) && array_key_exists($key, $output[$function_name]['parameters']) && $comment = $this->getBlockComment($key)) {
424        list($tags, $parameter_type, $options, $summary) = DojoFunctionDeclare::parseVariable($comment);
425
426        // If type is specified in the parameters, and it doesn't
427        // match the first word in this comment block, assume that
428        // this first word doesn't represent its type
429        if (!empty($output[$function_name]['parameters'][$key]['type']) && $parameter_type != $output[$function_name]['parameters'][$key]['type']) {
430          $summary = $comment;
431          $parameter_type = $output[$function_name]['parameters'][$key]['type'];
432        }
433        $output[$function_name]['parameters'][$key] = array_merge($output[$function_name]['parameters'][$key], $options);
434        $output[$function_name]['parameters'][$key]['type'] = $parameter_type;
435        $output[$function_name]['parameters'][$key]['summary'] = htmlentities($summary);
436      }
437    }
438
439    $returns = $this->getReturnComments();
440    if (count($returns)){
441      $output[$function_name]['returns'] = implode('|', $returns);
442    }
443
444    if ($output[$function_name]['example']) {
445      $output[$function_name]['examples'] = $output[$function_name]['example'];
446      unset($output[$function_name]['example']);
447    }
448
449    if($calls = $this->getThisInheritanceCalls()){
450      foreach ($calls as $call) {
451        $output[$function_name]['chains']['call'][] = $call;
452      }
453    }
454   
455    if($this->getPrototype()){
456      $output[$function_name]['prototype'] = $this->getPrototype();
457    }
458    if($this->getInstance()){
459      $output[$function_name]['instance'] = $this->getInstance();
460    }
461  }
462}
463
464?>
Note: See TracBrowser for help on using the repository browser.