| 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 |