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'; | 10 import 'dart:io'; |
11 | 11 |
12 import 'package:html/dom.dart' as dom; | 12 import 'package:html/dom.dart' as dom; |
13 import 'package:path/path.dart'; | 13 import 'package:path/path.dart'; |
14 | 14 |
15 import 'html.dart'; | 15 import 'html.dart'; |
16 import 'text_formatter.dart'; | 16 import 'text_formatter.dart'; |
17 | 17 |
| 18 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true); |
18 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$'); | 19 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$'); |
19 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true); | |
20 | 20 |
21 /** | 21 /** |
22 * Join the given strings using camelCase. If [doCapitalize] is true, the first | 22 * Join the given strings using camelCase. If [doCapitalize] is true, the first |
23 * part will be capitalized as well. | 23 * part will be capitalized as well. |
24 */ | 24 */ |
25 String camelJoin(List<String> parts, {bool doCapitalize: false}) { | 25 String camelJoin(List<String> parts, {bool doCapitalize: false}) { |
26 List<String> upcasedParts = <String>[]; | 26 List<String> upcasedParts = <String>[]; |
27 for (int i = 0; i < parts.length; i++) { | 27 for (int i = 0; i < parts.length; i++) { |
28 if (i == 0 && !doCapitalize) { | 28 if (i == 0 && !doCapitalize) { |
29 upcasedParts.add(parts[i]); | 29 upcasedParts.add(parts[i]); |
30 } else { | 30 } else { |
31 upcasedParts.add(capitalize(parts[i])); | 31 upcasedParts.add(capitalize(parts[i])); |
32 } | 32 } |
33 } | 33 } |
34 return upcasedParts.join(); | 34 return upcasedParts.join(); |
35 } | 35 } |
36 | 36 |
37 /** | 37 /** |
38 * Capitalize and return the passed String. | 38 * Capitalize and return the passed String. |
39 */ | 39 */ |
40 String capitalize(String string) { | 40 String capitalize(String string) { |
41 return string[0].toUpperCase() + string.substring(1); | 41 return string[0].toUpperCase() + string.substring(1); |
42 } | 42 } |
43 | 43 |
44 /** | 44 /** |
45 * Type of functions used to compute the contents of a set of generated files. | 45 * Type of functions used to compute the contents of a set of generated files. |
| 46 * [pkgPath] is the path to the current package. |
46 */ | 47 */ |
47 typedef Map<String, FileContentsComputer> DirectoryContentsComputer(); | 48 typedef Map<String, FileContentsComputer> DirectoryContentsComputer( |
| 49 String pkgPath); |
48 | 50 |
49 /** | 51 /** |
50 * Type of functions used to compute the contents of a generated file. | 52 * Type of functions used to compute the contents of a generated file. |
| 53 * [pkgPath] is the path to the current package. |
51 */ | 54 */ |
52 typedef String FileContentsComputer(); | 55 typedef String FileContentsComputer(String pkgPath); |
53 | 56 |
54 /** | 57 /** |
55 * Mixin class for generating code. | 58 * Mixin class for generating code. |
56 */ | 59 */ |
57 class CodeGenerator { | 60 class CodeGenerator { |
58 _CodeGeneratorState _state; | 61 _CodeGeneratorState _state; |
59 | 62 |
60 /** | 63 /** |
61 * Settings that specialize code generation behavior for a given | 64 * Settings that specialize code generation behavior for a given |
62 * programming language. | 65 * programming language. |
(...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
254 CodeGeneratorSettings( | 257 CodeGeneratorSettings( |
255 {this.languageName: 'java', | 258 {this.languageName: 'java', |
256 this.lineCommentLineLeader: '// ', | 259 this.lineCommentLineLeader: '// ', |
257 this.docCommentStartMarker: '/**', | 260 this.docCommentStartMarker: '/**', |
258 this.docCommentLineLeader: ' * ', | 261 this.docCommentLineLeader: ' * ', |
259 this.docCommentEndMarker: ' */', | 262 this.docCommentEndMarker: ' */', |
260 this.commentLineLength: 99, | 263 this.commentLineLength: 99, |
261 this.indent: ' '}); | 264 this.indent: ' '}); |
262 } | 265 } |
263 | 266 |
| 267 /** |
| 268 * Abstract base class representing behaviors common to generated files and |
| 269 * generated directories. |
| 270 */ |
264 abstract class GeneratedContent { | 271 abstract class GeneratedContent { |
265 FileSystemEntity get outputFile; | 272 /** |
266 bool check(); | 273 * Check whether the [output] has the correct contents, and return true if it |
267 void generate(); | 274 * does. [pkgPath] is the path to the current package. |
| 275 */ |
| 276 bool check(String pkgPath); |
| 277 |
| 278 /** |
| 279 * Replace the [output] with the correct contents. [pkgPath] is the path to |
| 280 * the current package. |
| 281 */ |
| 282 void generate(String pkgPath); |
| 283 |
| 284 /** |
| 285 * Get a [FileSystemEntity] representing the output file or directory. |
| 286 * [pkgPath] is the path to the current package. |
| 287 */ |
| 288 FileSystemEntity output(String pkgPath); |
268 } | 289 } |
269 | 290 |
270 /** | 291 /** |
271 * Class representing a single output directory (either generated code or | 292 * Class representing a single output directory (either generated code or |
272 * generated HTML). No other content should exist in the directory. | 293 * generated HTML). No other content should exist in the directory. |
273 */ | 294 */ |
274 class GeneratedDirectory extends GeneratedContent { | 295 class GeneratedDirectory extends GeneratedContent { |
275 /** | 296 /** |
276 * The path to the directory that will have the generated content. | 297 * The path to the directory that will have the generated content. |
277 */ | 298 */ |
278 final String outputDirPath; | 299 final String outputDirPath; |
279 | 300 |
280 /** | 301 /** |
281 * Callback function that computes the directory contents. | 302 * Callback function that computes the directory contents. |
282 */ | 303 */ |
283 final DirectoryContentsComputer directoryContentsComputer; | 304 final DirectoryContentsComputer directoryContentsComputer; |
284 | 305 |
285 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer); | 306 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer); |
286 | 307 |
287 /** | |
288 * Get a Directory object representing the output directory. | |
289 */ | |
290 Directory get outputFile => | |
291 new Directory(joinAll(posix.split(outputDirPath))); | |
292 | |
293 /** | |
294 * Check whether the directory has the correct contents, and return true if it | |
295 * does. | |
296 */ | |
297 @override | 308 @override |
298 bool check() { | 309 bool check(String pkgPath) { |
299 Map<String, FileContentsComputer> map = directoryContentsComputer(); | 310 Directory outputDirectory = output(pkgPath); |
| 311 Map<String, FileContentsComputer> map = directoryContentsComputer(pkgPath); |
300 try { | 312 try { |
301 for (String file in map.keys) { | 313 for (String file in map.keys) { |
302 FileContentsComputer fileContentsComputer = map[file]; | 314 FileContentsComputer fileContentsComputer = map[file]; |
303 String expectedContents = fileContentsComputer(); | 315 String expectedContents = fileContentsComputer(pkgPath); |
304 File outputFile = | 316 File outputFile = new File(posix.join(outputDirectory.path, file)); |
305 new File(joinAll(posix.split(posix.join(outputDirPath, file)))); | |
306 String actualContents = outputFile.readAsStringSync(); | 317 String actualContents = outputFile.readAsStringSync(); |
307 // Normalize Windows line endings to Unix line endings so that the | 318 // Normalize Windows line endings to Unix line endings so that the |
308 // comparison doesn't fail on Windows. | 319 // comparison doesn't fail on Windows. |
309 actualContents = actualContents.replaceAll('\r\n', '\n'); | 320 actualContents = actualContents.replaceAll('\r\n', '\n'); |
310 if (expectedContents != actualContents) { | 321 if (expectedContents != actualContents) { |
311 return false; | 322 return false; |
312 } | 323 } |
313 } | 324 } |
314 int nonHiddenFileCount = 0; | 325 int nonHiddenFileCount = 0; |
315 outputFile | 326 outputDirectory |
316 .listSync(recursive: false, followLinks: false) | 327 .listSync(recursive: false, followLinks: false) |
317 .forEach((FileSystemEntity fileSystemEntity) { | 328 .forEach((FileSystemEntity fileSystemEntity) { |
318 if (fileSystemEntity is File && | 329 if (fileSystemEntity is File && |
319 !basename(fileSystemEntity.path).startsWith('.')) { | 330 !basename(fileSystemEntity.path).startsWith('.')) { |
320 nonHiddenFileCount++; | 331 nonHiddenFileCount++; |
321 } | 332 } |
322 }); | 333 }); |
323 if (nonHiddenFileCount != map.length) { | 334 if (nonHiddenFileCount != map.length) { |
324 // The number of files generated doesn't match the number we expected to | 335 // The number of files generated doesn't match the number we expected to |
325 // generate. | 336 // generate. |
326 return false; | 337 return false; |
327 } | 338 } |
328 } catch (e) { | 339 } catch (e) { |
329 // There was a problem reading the file (most likely because it didn't | 340 // There was a problem reading the file (most likely because it didn't |
330 // exist). Treat that the same as if the file doesn't have the expected | 341 // exist). Treat that the same as if the file doesn't have the expected |
331 // contents. | 342 // contents. |
332 return false; | 343 return false; |
333 } | 344 } |
334 return true; | 345 return true; |
335 } | 346 } |
336 | 347 |
337 /** | |
338 * Replace the directory with the correct contents. [spec] is the "tool/spec" | |
339 * directory. If [spec] is unspecified, it is assumed to be the directory | |
340 * containing Platform.executable. | |
341 */ | |
342 @override | 348 @override |
343 void generate() { | 349 void generate(String pkgPath) { |
| 350 Directory outputDirectory = output(pkgPath); |
344 try { | 351 try { |
345 // delete the contents of the directory (and the directory itself) | 352 // delete the contents of the directory (and the directory itself) |
346 outputFile.deleteSync(recursive: true); | 353 outputDirectory.deleteSync(recursive: true); |
347 } catch (e) { | 354 } catch (e) { |
348 // Error caught while trying to delete the directory, this can happen if | 355 // Error caught while trying to delete the directory, this can happen if |
349 // it didn't yet exist. | 356 // it didn't yet exist. |
350 } | 357 } |
351 // re-create the empty directory | 358 // re-create the empty directory |
352 outputFile.createSync(recursive: true); | 359 outputDirectory.createSync(recursive: true); |
353 | 360 |
354 // generate all of the files in the directory | 361 // generate all of the files in the directory |
355 Map<String, FileContentsComputer> map = directoryContentsComputer(); | 362 Map<String, FileContentsComputer> map = directoryContentsComputer(pkgPath); |
356 map.forEach((String file, FileContentsComputer fileContentsComputer) { | 363 map.forEach((String file, FileContentsComputer fileContentsComputer) { |
357 File outputFile = new File(joinAll(posix.split(outputDirPath + file))); | 364 File outputFile = new File(posix.join(outputDirectory.path, file)); |
358 outputFile.writeAsStringSync(fileContentsComputer()); | 365 outputFile.writeAsStringSync(fileContentsComputer(pkgPath)); |
359 }); | 366 }); |
360 } | 367 } |
| 368 |
| 369 @override |
| 370 Directory output(String pkgPath) => |
| 371 new Directory(join(pkgPath, joinAll(posix.split(outputDirPath)))); |
361 } | 372 } |
362 | 373 |
363 /** | 374 /** |
364 * Class representing a single output file (either generated code or generated | 375 * Class representing a single output file (either generated code or generated |
365 * HTML). | 376 * HTML). |
366 */ | 377 */ |
367 class GeneratedFile extends GeneratedContent { | 378 class GeneratedFile extends GeneratedContent { |
368 /** | 379 /** |
369 * The output file to which generated output should be written, relative to | 380 * The output file to which generated output should be written, relative to |
370 * the "tool/spec" directory. This filename uses the posix path separator | 381 * the "tool/spec" directory. This filename uses the posix path separator |
371 * ('/') regardless of the OS. | 382 * ('/') regardless of the OS. |
372 */ | 383 */ |
373 final String outputPath; | 384 final String outputPath; |
374 | 385 |
375 /** | 386 /** |
376 * Callback function which computes the file. | 387 * Callback function which computes the file. |
377 */ | 388 */ |
378 final FileContentsComputer computeContents; | 389 final FileContentsComputer computeContents; |
379 | 390 |
380 GeneratedFile(this.outputPath, this.computeContents); | 391 GeneratedFile(this.outputPath, this.computeContents); |
381 | 392 |
382 /** | |
383 * Get a File object representing the output file. | |
384 */ | |
385 File get outputFile => new File(joinAll(posix.split(outputPath))); | |
386 | |
387 /** | |
388 * Check whether the file has the correct contents, and return true if it | |
389 * does. | |
390 */ | |
391 @override | 393 @override |
392 bool check() { | 394 bool check(String pkgPath) { |
393 String expectedContents = computeContents(); | 395 File outputFile = output(pkgPath); |
| 396 String expectedContents = computeContents(pkgPath); |
394 try { | 397 try { |
395 String actualContents = outputFile.readAsStringSync(); | 398 String actualContents = outputFile.readAsStringSync(); |
396 // Normalize Windows line endings to Unix line endings so that the | 399 // Normalize Windows line endings to Unix line endings so that the |
397 // comparison doesn't fail on Windows. | 400 // comparison doesn't fail on Windows. |
398 actualContents = actualContents.replaceAll('\r\n', '\n'); | 401 actualContents = actualContents.replaceAll('\r\n', '\n'); |
399 return expectedContents == actualContents; | 402 return expectedContents == actualContents; |
400 } catch (e) { | 403 } catch (e) { |
401 // There was a problem reading the file (most likely because it didn't | 404 // There was a problem reading the file (most likely because it didn't |
402 // exist). Treat that the same as if the file doesn't have the expected | 405 // exist). Treat that the same as if the file doesn't have the expected |
403 // contents. | 406 // contents. |
404 return false; | 407 return false; |
405 } | 408 } |
406 } | 409 } |
407 | 410 |
408 /** | 411 @override |
409 * Replace the file with the correct contents. [spec] is the "tool/spec" | 412 void generate(String pkgPath) { |
410 * directory. If [spec] is unspecified, it is assumed to be the directory | 413 output(pkgPath).writeAsStringSync(computeContents(pkgPath)); |
411 * containing Platform.executable. | |
412 */ | |
413 void generate() { | |
414 outputFile.writeAsStringSync(computeContents()); | |
415 } | 414 } |
| 415 |
| 416 @override |
| 417 File output(String pkgPath) => |
| 418 new File(join(pkgPath, joinAll(posix.split(outputPath)))); |
416 } | 419 } |
417 | 420 |
418 /** | 421 /** |
419 * Mixin class for generating HTML representations of code that are suitable | 422 * Mixin class for generating HTML representations of code that are suitable |
420 * for enclosing inside a <pre> element. | 423 * for enclosing inside a <pre> element. |
421 */ | 424 */ |
422 abstract class HtmlCodeGenerator { | 425 abstract class HtmlCodeGenerator { |
423 _HtmlCodeGeneratorState _state; | 426 _HtmlCodeGeneratorState _state; |
424 | 427 |
425 /** | 428 /** |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
548 if (lines.last.isEmpty) { | 551 if (lines.last.isEmpty) { |
549 lines.removeLast(); | 552 lines.removeLast(); |
550 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); | 553 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); |
551 indentNeeded = true; | 554 indentNeeded = true; |
552 } else { | 555 } else { |
553 buffer.add(new dom.Text(lines.join('\n$indent'))); | 556 buffer.add(new dom.Text(lines.join('\n$indent'))); |
554 indentNeeded = false; | 557 indentNeeded = false; |
555 } | 558 } |
556 } | 559 } |
557 } | 560 } |
OLD | NEW |