OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Tools for generating code in analyzer and analysis server. | 6 * Tools for generating code in analyzer and analysis server. |
7 */ | 7 */ |
8 library analyzer.src.codegen.tools; | 8 library analyzer.src.codegen.tools; |
9 | 9 |
10 import 'dart:io'; | |
11 | |
12 import 'package:analyzer/src/codegen/html.dart'; | 10 import 'package:analyzer/src/codegen/html.dart'; |
13 import 'package:analyzer/src/codegen/text_formatter.dart'; | 11 import 'package:analyzer/src/codegen/text_formatter.dart'; |
14 import 'package:html/dom.dart' as dom; | 12 import 'package:html/dom.dart' as dom; |
15 import 'package:path/path.dart'; | |
16 | 13 |
17 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true); | 14 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true); |
18 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$'); | 15 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$'); |
19 | 16 |
20 /** | 17 /** |
21 * Join the given strings using camelCase. If [doCapitalize] is true, the first | 18 * Join the given strings using camelCase. If [doCapitalize] is true, the first |
22 * part will be capitalized as well. | 19 * part will be capitalized as well. |
23 */ | 20 */ |
24 String camelJoin(List<String> parts, {bool doCapitalize: false}) { | 21 String camelJoin(List<String> parts, {bool doCapitalize: false}) { |
25 List<String> upcasedParts = <String>[]; | 22 List<String> upcasedParts = <String>[]; |
26 for (int i = 0; i < parts.length; i++) { | 23 for (int i = 0; i < parts.length; i++) { |
27 if (i == 0 && !doCapitalize) { | 24 if (i == 0 && !doCapitalize) { |
28 upcasedParts.add(parts[i]); | 25 upcasedParts.add(parts[i]); |
29 } else { | 26 } else { |
30 upcasedParts.add(capitalize(parts[i])); | 27 upcasedParts.add(capitalize(parts[i])); |
31 } | 28 } |
32 } | 29 } |
33 return upcasedParts.join(); | 30 return upcasedParts.join(); |
34 } | 31 } |
35 | 32 |
36 /** | 33 /** |
37 * Capitalize and return the passed String. | 34 * Capitalize and return the passed String. |
38 */ | 35 */ |
39 String capitalize(String string) { | 36 String capitalize(String string) { |
40 return string[0].toUpperCase() + string.substring(1); | 37 return string[0].toUpperCase() + string.substring(1); |
41 } | 38 } |
42 | 39 |
43 /** | 40 /** |
44 * Type of functions used to compute the contents of a set of generated files. | |
45 * [pkgPath] is the path to the current package. | |
46 */ | |
47 typedef Map<String, FileContentsComputer> DirectoryContentsComputer( | |
48 String pkgPath); | |
49 | |
50 /** | |
51 * Type of functions used to compute the contents of a generated file. | |
52 * [pkgPath] is the path to the current package. | |
53 */ | |
54 typedef String FileContentsComputer(String pkgPath); | |
55 | |
56 /** | |
57 * Mixin class for generating code. | 41 * Mixin class for generating code. |
58 */ | 42 */ |
59 class CodeGenerator { | 43 class CodeGenerator { |
60 _CodeGeneratorState _state; | 44 _CodeGeneratorState _state; |
61 | 45 |
62 /** | 46 /** |
63 * Settings that specialize code generation behavior for a given | 47 * Settings that specialize code generation behavior for a given |
64 * programming language. | 48 * programming language. |
65 */ | 49 */ |
66 CodeGeneratorSettings codeGeneratorSettings = new CodeGeneratorSettings(); | 50 CodeGeneratorSettings codeGeneratorSettings = new CodeGeneratorSettings(); |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
259 {this.languageName: 'java', | 243 {this.languageName: 'java', |
260 this.lineCommentLineLeader: '// ', | 244 this.lineCommentLineLeader: '// ', |
261 this.docCommentStartMarker: '/**', | 245 this.docCommentStartMarker: '/**', |
262 this.docCommentLineLeader: ' * ', | 246 this.docCommentLineLeader: ' * ', |
263 this.docCommentEndMarker: ' */', | 247 this.docCommentEndMarker: ' */', |
264 this.commentLineLength: 99, | 248 this.commentLineLength: 99, |
265 this.indent: ' '}); | 249 this.indent: ' '}); |
266 } | 250 } |
267 | 251 |
268 /** | 252 /** |
269 * Abstract base class representing behaviors common to generated files and | |
270 * generated directories. | |
271 */ | |
272 abstract class GeneratedContent { | |
273 /** | |
274 * Check whether the [output] has the correct contents, and return true if it | |
275 * does. [pkgPath] is the path to the current package. | |
276 */ | |
277 bool check(String pkgPath); | |
278 | |
279 /** | |
280 * Replace the [output] with the correct contents. [pkgPath] is the path to | |
281 * the current package. | |
282 */ | |
283 void generate(String pkgPath); | |
284 | |
285 /** | |
286 * Get a [FileSystemEntity] representing the output file or directory. | |
287 * [pkgPath] is the path to the current package. | |
288 */ | |
289 FileSystemEntity output(String pkgPath); | |
290 | |
291 /** | |
292 * Check that all of the [targets] are up to date. If they are not, print | |
293 * out a message instructing the user to regenerate them, and exit with a | |
294 * nonzero error code. | |
295 * | |
296 * [pkgPath] is the path to the current package. [generatorRelPath] is the | |
297 * path to a .dart script the user may use to regenerate the targets. | |
298 * | |
299 * To avoid mistakes when run on Windows, [generatorRelPath] always uses | |
300 * POSIX directory separators. | |
301 */ | |
302 static void checkAll(String pkgPath, String generatorRelPath, | |
303 Iterable<GeneratedContent> targets) { | |
304 bool generateNeeded = false; | |
305 for (GeneratedContent target in targets) { | |
306 if (!target.check(pkgPath)) { | |
307 print( | |
308 '${target.output(pkgPath).absolute} does not have expected contents.
'); | |
309 generateNeeded = true; | |
310 } | |
311 } | |
312 if (generateNeeded) { | |
313 print('Please regenerate using:'); | |
314 String executable = Platform.executable; | |
315 String packageRoot = ''; | |
316 if (Platform.packageRoot != null) { | |
317 packageRoot = ' --package-root=${Platform.packageRoot}'; | |
318 } | |
319 String generateScript = | |
320 join(pkgPath, joinAll(posix.split(generatorRelPath))); | |
321 print(' $executable$packageRoot $generateScript'); | |
322 exit(1); | |
323 } else { | |
324 print('All generated files up to date.'); | |
325 } | |
326 } | |
327 | |
328 /** | |
329 * Regenerate all of the [targets]. [pkgPath] is the path to the current | |
330 * package. | |
331 */ | |
332 static void generateAll(String pkgPath, Iterable<GeneratedContent> targets) { | |
333 for (GeneratedContent target in targets) { | |
334 target.generate(pkgPath); | |
335 } | |
336 } | |
337 } | |
338 | |
339 /** | |
340 * Class representing a single output directory (either generated code or | |
341 * generated HTML). No other content should exist in the directory. | |
342 */ | |
343 class GeneratedDirectory extends GeneratedContent { | |
344 /** | |
345 * The path to the directory that will have the generated content. | |
346 */ | |
347 final String outputDirPath; | |
348 | |
349 /** | |
350 * Callback function that computes the directory contents. | |
351 */ | |
352 final DirectoryContentsComputer directoryContentsComputer; | |
353 | |
354 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer); | |
355 | |
356 @override | |
357 bool check(String pkgPath) { | |
358 Directory outputDirectory = output(pkgPath); | |
359 Map<String, FileContentsComputer> map = directoryContentsComputer(pkgPath); | |
360 try { | |
361 for (String file in map.keys) { | |
362 FileContentsComputer fileContentsComputer = map[file]; | |
363 String expectedContents = fileContentsComputer(pkgPath); | |
364 File outputFile = new File(posix.join(outputDirectory.path, file)); | |
365 String actualContents = outputFile.readAsStringSync(); | |
366 // Normalize Windows line endings to Unix line endings so that the | |
367 // comparison doesn't fail on Windows. | |
368 actualContents = actualContents.replaceAll('\r\n', '\n'); | |
369 if (expectedContents != actualContents) { | |
370 return false; | |
371 } | |
372 } | |
373 int nonHiddenFileCount = 0; | |
374 outputDirectory | |
375 .listSync(recursive: false, followLinks: false) | |
376 .forEach((FileSystemEntity fileSystemEntity) { | |
377 if (fileSystemEntity is File && | |
378 !basename(fileSystemEntity.path).startsWith('.')) { | |
379 nonHiddenFileCount++; | |
380 } | |
381 }); | |
382 if (nonHiddenFileCount != map.length) { | |
383 // The number of files generated doesn't match the number we expected to | |
384 // generate. | |
385 return false; | |
386 } | |
387 } catch (e) { | |
388 // There was a problem reading the file (most likely because it didn't | |
389 // exist). Treat that the same as if the file doesn't have the expected | |
390 // contents. | |
391 return false; | |
392 } | |
393 return true; | |
394 } | |
395 | |
396 @override | |
397 void generate(String pkgPath) { | |
398 Directory outputDirectory = output(pkgPath); | |
399 try { | |
400 // delete the contents of the directory (and the directory itself) | |
401 outputDirectory.deleteSync(recursive: true); | |
402 } catch (e) { | |
403 // Error caught while trying to delete the directory, this can happen if | |
404 // it didn't yet exist. | |
405 } | |
406 // re-create the empty directory | |
407 outputDirectory.createSync(recursive: true); | |
408 | |
409 // generate all of the files in the directory | |
410 Map<String, FileContentsComputer> map = directoryContentsComputer(pkgPath); | |
411 map.forEach((String file, FileContentsComputer fileContentsComputer) { | |
412 File outputFile = new File(posix.join(outputDirectory.path, file)); | |
413 outputFile.writeAsStringSync(fileContentsComputer(pkgPath)); | |
414 }); | |
415 } | |
416 | |
417 @override | |
418 Directory output(String pkgPath) => | |
419 new Directory(join(pkgPath, joinAll(posix.split(outputDirPath)))); | |
420 } | |
421 | |
422 /** | |
423 * Class representing a single output file (either generated code or generated | |
424 * HTML). | |
425 */ | |
426 class GeneratedFile extends GeneratedContent { | |
427 /** | |
428 * The output file to which generated output should be written, relative to | |
429 * the "tool/spec" directory. This filename uses the posix path separator | |
430 * ('/') regardless of the OS. | |
431 */ | |
432 final String outputPath; | |
433 | |
434 /** | |
435 * Callback function which computes the file. | |
436 */ | |
437 final FileContentsComputer computeContents; | |
438 | |
439 GeneratedFile(this.outputPath, this.computeContents); | |
440 | |
441 @override | |
442 bool check(String pkgPath) { | |
443 File outputFile = output(pkgPath); | |
444 String expectedContents = computeContents(pkgPath); | |
445 try { | |
446 String actualContents = outputFile.readAsStringSync(); | |
447 // Normalize Windows line endings to Unix line endings so that the | |
448 // comparison doesn't fail on Windows. | |
449 actualContents = actualContents.replaceAll('\r\n', '\n'); | |
450 return expectedContents == actualContents; | |
451 } catch (e) { | |
452 // There was a problem reading the file (most likely because it didn't | |
453 // exist). Treat that the same as if the file doesn't have the expected | |
454 // contents. | |
455 return false; | |
456 } | |
457 } | |
458 | |
459 @override | |
460 void generate(String pkgPath) { | |
461 output(pkgPath).writeAsStringSync(computeContents(pkgPath)); | |
462 } | |
463 | |
464 @override | |
465 File output(String pkgPath) => | |
466 new File(join(pkgPath, joinAll(posix.split(outputPath)))); | |
467 } | |
468 | |
469 /** | |
470 * Mixin class for generating HTML representations of code that are suitable | 253 * Mixin class for generating HTML representations of code that are suitable |
471 * for enclosing inside a <pre> element. | 254 * for enclosing inside a <pre> element. |
472 */ | 255 */ |
473 abstract class HtmlCodeGenerator { | 256 abstract class HtmlCodeGenerator { |
474 _HtmlCodeGeneratorState _state; | 257 _HtmlCodeGeneratorState _state; |
475 | 258 |
476 /** | 259 /** |
477 * Add the given [node] to the HTML output. | 260 * Add the given [node] to the HTML output. |
478 */ | 261 */ |
479 void add(dom.Node node) { | 262 void add(dom.Node node) { |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
600 if (lines.last.isEmpty) { | 383 if (lines.last.isEmpty) { |
601 lines.removeLast(); | 384 lines.removeLast(); |
602 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); | 385 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); |
603 indentNeeded = true; | 386 indentNeeded = true; |
604 } else { | 387 } else { |
605 buffer.add(new dom.Text(lines.join('\n$indent'))); | 388 buffer.add(new dom.Text(lines.join('\n$indent'))); |
606 indentNeeded = false; | 389 indentNeeded = false; |
607 } | 390 } |
608 } | 391 } |
609 } | 392 } |
OLD | NEW |