Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(247)

Side by Side Diff: packages/analyzer/lib/src/codegen/tools.dart

Issue 2990843002: Removed fixed dependencies (Closed)
Patch Set: Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Tools for generating code in analyzer and analysis server.
7 */
8 library analyzer.src.codegen.tools;
9
10 import 'dart:io';
11
12 import 'package:analyzer/src/codegen/html.dart';
13 import 'package:analyzer/src/codegen/text_formatter.dart';
14 import 'package:html/dom.dart' as dom;
15 import 'package:path/path.dart';
16
17 final RegExp trailingSpacesInLineRegExp = new RegExp(r' +$', multiLine: true);
18 final RegExp trailingWhitespaceRegExp = new RegExp(r'[\n ]+$');
19
20 /**
21 * Join the given strings using camelCase. If [doCapitalize] is true, the first
22 * part will be capitalized as well.
23 */
24 String camelJoin(List<String> parts, {bool doCapitalize: false}) {
25 List<String> upcasedParts = <String>[];
26 for (int i = 0; i < parts.length; i++) {
27 if (i == 0 && !doCapitalize) {
28 upcasedParts.add(parts[i]);
29 } else {
30 upcasedParts.add(capitalize(parts[i]));
31 }
32 }
33 return upcasedParts.join();
34 }
35
36 /**
37 * Capitalize and return the passed String.
38 */
39 String capitalize(String string) {
40 return string[0].toUpperCase() + string.substring(1);
41 }
42
43 /**
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.
58 */
59 class CodeGenerator {
60 _CodeGeneratorState _state;
61
62 /**
63 * Settings that specialize code generation behavior for a given
64 * programming language.
65 */
66 CodeGeneratorSettings codeGeneratorSettings = new CodeGeneratorSettings();
67
68 /**
69 * Measure the width of the current indentation level.
70 */
71 int get indentWidth => _state.nextIndent.length;
72
73 /**
74 * Execute [callback], collecting any code that is output using [write]
75 * or [writeln], and return the result as a string.
76 */
77 String collectCode(void callback(), {bool removeTrailingNewLine: false}) {
78 _CodeGeneratorState oldState = _state;
79 try {
80 _state = new _CodeGeneratorState();
81 callback();
82 var text =
83 _state.buffer.toString().replaceAll(trailingSpacesInLineRegExp, '');
84 if (!removeTrailingNewLine) {
85 return text;
86 } else {
87 return text.replaceAll(trailingWhitespaceRegExp, '');
88 }
89 } finally {
90 _state = oldState;
91 }
92 }
93
94 /**
95 * Generate a doc comment based on the HTML in [docs].
96 *
97 * When generating java code, the output is compatible with Javadoc, which
98 * understands certain HTML constructs.
99 */
100 void docComment(List<dom.Node> docs, {bool removeTrailingNewLine: false}) {
101 if (containsOnlyWhitespace(docs)) return;
102 if (codeGeneratorSettings.docCommentStartMarker != null)
103 writeln(codeGeneratorSettings.docCommentStartMarker);
104 int width = codeGeneratorSettings.commentLineLength;
105 bool javadocStyle = codeGeneratorSettings.languageName == 'java';
106 indentBy(codeGeneratorSettings.docCommentLineLeader, () {
107 write(nodesToText(docs, width - _state.indent.length, javadocStyle,
108 removeTrailingNewLine: removeTrailingNewLine));
109 });
110 if (codeGeneratorSettings.docCommentEndMarker != null)
111 writeln(codeGeneratorSettings.docCommentEndMarker);
112 }
113
114 /**
115 * Execute [callback], indenting any code it outputs.
116 */
117 void indent(void callback()) {
118 indentSpecial(
119 codeGeneratorSettings.indent, codeGeneratorSettings.indent, callback);
120 }
121
122 /**
123 * Execute [callback], using [additionalIndent] to indent any code it outputs.
124 */
125 void indentBy(String additionalIndent, void callback()) =>
126 indentSpecial(additionalIndent, additionalIndent, callback);
127
128 /**
129 * Execute [callback], using [additionalIndent] to indent any code it outputs.
130 * The first line of output is indented by [firstAdditionalIndent] instead of
131 * [additionalIndent].
132 */
133 void indentSpecial(
134 String firstAdditionalIndent, String additionalIndent, void callback()) {
135 String oldNextIndent = _state.nextIndent;
136 String oldIndent = _state.indent;
137 try {
138 _state.nextIndent += firstAdditionalIndent;
139 _state.indent += additionalIndent;
140 callback();
141 } finally {
142 _state.nextIndent = oldNextIndent;
143 _state.indent = oldIndent;
144 }
145 }
146
147 void lineComment(List<dom.Node> docs) {
148 if (containsOnlyWhitespace(docs)) {
149 return;
150 }
151 write(codeGeneratorSettings.lineCommentLineLeader);
152 int width = codeGeneratorSettings.commentLineLength;
153 indentBy(codeGeneratorSettings.lineCommentLineLeader, () {
154 write(nodesToText(docs, width - _state.indent.length, false));
155 });
156 }
157
158 void outputHeader({bool javaStyle: false}) {
159 String header;
160 if (codeGeneratorSettings.languageName == 'java') {
161 header = '''
162 /*
163 * Copyright (c) 2015, the Dart project authors.
164 *
165 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u se this file except
166 * in compliance with the License. You may obtain a copy of the License at
167 *
168 * http://www.eclipse.org/legal/epl-v10.html
169 *
170 * Unless required by applicable law or agreed to in writing, software distribut ed under the License
171 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY K IND, either express
172 * or implied. See the License for the specific language governing permissions a nd limitations under
173 * the License.
174 *
175 * This file has been automatically generated. Please do not edit it manually.
176 * To regenerate the file, use the script "pkg/analysis_server/tool/spec/generat e_files".
177 */''';
178 } else if (codeGeneratorSettings.languageName == 'python') {
179 header = '''
180 # Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
181 # for details. All rights reserved. Use of this source code is governed by a
182 # BSD-style license that can be found in the LICENSE file.
183 #
184 # This file has been automatically generated. Please do not edit it manually.
185 # To regenerate the file, use the script
186 # "pkg/analysis_server/tool/spec/generate_files".
187 ''';
188 } else {
189 header = '''
190 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
191 // for details. All rights reserved. Use of this source code is governed by a
192 // BSD-style license that can be found in the LICENSE file.
193 //
194 // This file has been automatically generated. Please do not edit it manually.
195 // To regenerate the file, use the script
196 // "pkg/analysis_server/tool/spec/generate_files".
197 ''';
198 }
199 writeln(header.trim());
200 }
201
202 /**
203 * Output text without ending the current line.
204 */
205 void write(Object obj) {
206 _state.write(obj.toString());
207 }
208
209 /**
210 * Output text, ending the current line.
211 */
212 void writeln([Object obj = '']) {
213 _state.write('$obj\n');
214 }
215 }
216
217 /**
218 * Controls several settings of [CodeGenerator].
219 *
220 * The default settings are valid for generating Java and Dart code.
221 */
222 class CodeGeneratorSettings {
223 /**
224 * Name of the language being generated. Lowercase.
225 */
226 String languageName;
227
228 /**
229 * Marker used in line comments.
230 */
231 String lineCommentLineLeader;
232
233 /**
234 * Start marker for doc comments.
235 */
236 String docCommentStartMarker;
237
238 /**
239 * Line leader for body lines in doc comments.
240 */
241 String docCommentLineLeader;
242
243 /**
244 * End marker for doc comments.
245 */
246 String docCommentEndMarker;
247
248 /**
249 * Line length for doc comment lines.
250 */
251 int commentLineLength;
252
253 /**
254 * String used for indenting code.
255 */
256 String indent;
257
258 CodeGeneratorSettings(
259 {this.languageName: 'java',
260 this.lineCommentLineLeader: '// ',
261 this.docCommentStartMarker: '/**',
262 this.docCommentLineLeader: ' * ',
263 this.docCommentEndMarker: ' */',
264 this.commentLineLength: 99,
265 this.indent: ' '});
266 }
267
268 /**
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
471 * for enclosing inside a <pre> element.
472 */
473 abstract class HtmlCodeGenerator {
474 _HtmlCodeGeneratorState _state;
475
476 /**
477 * Add the given [node] to the HTML output.
478 */
479 void add(dom.Node node) {
480 _state.add(node);
481 }
482
483 /**
484 * Add the given [nodes] to the HTML output.
485 */
486 void addAll(Iterable<dom.Node> nodes) {
487 for (dom.Node node in nodes) {
488 _state.add(node);
489 }
490 }
491
492 /**
493 * Execute [callback], collecting any code that is output using [write],
494 * [writeln], [add], or [addAll], and return the result as a list of DOM
495 * nodes.
496 */
497 List<dom.Node> collectHtml(void callback()) {
498 _HtmlCodeGeneratorState oldState = _state;
499 try {
500 _state = new _HtmlCodeGeneratorState();
501 if (callback != null) {
502 callback();
503 }
504 return _state.buffer;
505 } finally {
506 _state = oldState;
507 }
508 }
509
510 /**
511 * Execute [callback], wrapping its output in an element with the given
512 * [name] and [attributes].
513 */
514 void element(String name, Map<dynamic, String> attributes,
515 [void callback()]) {
516 add(makeElement(name, attributes, collectHtml(callback)));
517 }
518
519 /**
520 * Execute [callback], indenting any code it outputs by two spaces.
521 */
522 void indent(void callback()) {
523 String oldIndent = _state.indent;
524 try {
525 _state.indent += ' ';
526 callback();
527 } finally {
528 _state.indent = oldIndent;
529 }
530 }
531
532 /**
533 * Output text without ending the current line.
534 */
535 void write(Object obj) {
536 _state.write(obj.toString());
537 }
538
539 /**
540 * Output text, ending the current line.
541 */
542 void writeln([Object obj = '']) {
543 _state.write('$obj\n');
544 }
545 }
546
547 /**
548 * State used by [CodeGenerator].
549 */
550 class _CodeGeneratorState {
551 StringBuffer buffer = new StringBuffer();
552 String nextIndent = '';
553 String indent = '';
554 bool indentNeeded = true;
555
556 void write(String text) {
557 List<String> lines = text.split('\n');
558 for (int i = 0; i < lines.length; i++) {
559 if (i == lines.length - 1 && lines[i].isEmpty) {
560 break;
561 }
562 if (indentNeeded) {
563 buffer.write(nextIndent);
564 nextIndent = indent;
565 }
566 indentNeeded = false;
567 buffer.write(lines[i]);
568 if (i != lines.length - 1) {
569 buffer.writeln();
570 indentNeeded = true;
571 }
572 }
573 }
574 }
575
576 /**
577 * State used by [HtmlCodeGenerator].
578 */
579 class _HtmlCodeGeneratorState {
580 List<dom.Node> buffer = <dom.Node>[];
581 String indent = '';
582 bool indentNeeded = true;
583
584 void add(dom.Node node) {
585 if (node is dom.Text) {
586 write(node.text);
587 } else {
588 buffer.add(node);
589 }
590 }
591
592 void write(String text) {
593 if (text.isEmpty) {
594 return;
595 }
596 if (indentNeeded) {
597 buffer.add(new dom.Text(indent));
598 }
599 List<String> lines = text.split('\n');
600 if (lines.last.isEmpty) {
601 lines.removeLast();
602 buffer.add(new dom.Text(lines.join('\n$indent') + '\n'));
603 indentNeeded = true;
604 } else {
605 buffer.add(new dom.Text(lines.join('\n$indent')));
606 indentNeeded = false;
607 }
608 }
609 }
OLDNEW
« no previous file with comments | « packages/analyzer/lib/src/codegen/text_formatter.dart ('k') | packages/analyzer/lib/src/context/builder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698