Files
sdk/pkg/analyzer_utilities/lib/text_formatter.dart
Paul Berry f4ff72aadd Migrate analyzer_utilities package to new constructor decl syntax.
This change migrates the analyzer_utilities package to use the new
constructor declaration syntax, described in
https://github.com/dart-lang/language/blob/main/accepted/future-releases/primary-constructors/feature-specification.md#abbreviations-of-in-body-constructor-declarations.

This change was performed in an automated fashion, by (a) bumping the
packages' SDK constraints to `3.13.0-0`, (b) enabling the lints
`unnecessary_type_name_in_constructor` and
`unnecessary_const_in_enum_constructor`, (c) fixing the resulting lint
failures using `dart fix`, and then (d) reformatting the affected
files.

To ease code review, I've reverted unrelated formatting changes.

Since this change requires bumping SDK constaints to `3.13.0-0`, it
was only performed on packages that are *not* published on
pub. (Packages that *are* published on pub should remain on lower
language versions until at least after the stable version of 3.13 is
released, so that we don't block users on the stable channel from
receiving updates to those packages.)

Change-Id: Ib9564fe588b1118f7e810bd39ff9c6576a6a6964
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505066
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2026-05-21 12:49:15 -07:00

223 lines
5.7 KiB
Dart

// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Code for converting HTML into text, for use during code generation of
/// analyzer and analysis server.
library;
import 'package:analyzer_utilities/html_dom.dart' as dom;
import 'package:analyzer_utilities/tools.dart';
final RegExp whitespace = RegExp(r'\s');
/// Converts the HTML in [desc] into text, word wrapping at width [width].
///
/// If [javadocStyle] is `true`, then the output is compatible with Javadoc,
/// which understands certain HTML constructs.
String nodesToText(
List<dom.Node> desc,
int width,
bool javadocStyle, {
bool removeTrailingNewLine = false,
}) {
var formatter = _TextFormatter(width, javadocStyle);
return formatter.collectCode(() {
formatter.addAll(desc);
formatter.lineBreak(false);
}, removeTrailingNewLine: removeTrailingNewLine);
}
/// Engine that transforms HTML to text. The input HTML is processed one
/// character at a time, gathering characters into words and words into lines.
class _TextFormatter with CodeGenerator {
/// Word-wrapping width.
final int width;
/// The word currently being gathered.
String word = '';
/// The line currently being gathered.
String line = '';
/// True if a blank line should be inserted before the next word.
bool verticalSpaceNeeded = false;
/// True if no text has been output yet. This suppresses blank lines.
bool atStart = true;
/// Whether we are processing a `<pre>` element, thus whitespace should be
/// preserved.
bool preserveSpaces = false;
/// True if the output should be Javadoc compatible.
final bool javadocStyle;
new(this.width, this.javadocStyle);
/// Process an HTML node.
void add(dom.Node node) {
if (node is dom.Text) {
for (var char in node.text.split('')) {
if (preserveSpaces) {
wordBreak();
write(escape(char));
} else if (whitespace.hasMatch(char)) {
wordBreak();
} else {
resolveVerticalSpace();
word += escape(char);
}
}
} else if (node is dom.Element) {
switch (node.name) {
case 'br':
lineBreak(false);
case 'dl':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'p':
lineBreak(true);
addAll(node.nodes);
lineBreak(true);
case 'div':
lineBreak(false);
if (node.classes.contains('hangingIndent')) {
resolveVerticalSpace();
indentSpecial('', ' ', () {
addAll(node.nodes);
lineBreak(false);
});
} else {
addAll(node.nodes);
lineBreak(false);
}
case 'ul':
lineBreak(false);
addAll(node.nodes);
lineBreak(false);
case 'li':
lineBreak(false);
resolveVerticalSpace();
indentSpecial('- ', ' ', () {
addAll(node.nodes);
lineBreak(false);
});
case 'dt':
word += '* `';
addAll(node.nodes);
word += '`';
case 'dd':
lineBreak(true);
indent(() {
addAll(node.nodes);
lineBreak(true);
});
case 'pre':
lineBreak(false);
resolveVerticalSpace();
if (javadocStyle) {
writeln('<pre>');
}
var oldPreserveSpaces = preserveSpaces;
try {
preserveSpaces = true;
// Indent twice in order to format `node.nodes` as Markdown
// pre-formatted text.
indent(() {
indent(() {
addAll(node.nodes);
});
});
} finally {
preserveSpaces = oldPreserveSpaces;
}
writeln();
if (javadocStyle) {
writeln('</pre>');
}
lineBreak(false);
case 'tt':
word += javadocStyle ? '<code>' : '`';
addAll(node.nodes);
word += javadocStyle ? '</code>' : '`';
case 'a':
case 'b':
case 'body':
case 'html':
case 'i':
case 'span':
addAll(node.nodes);
case 'head':
break;
default:
throw Exception('Unexpected HTML element: ${node.name}');
}
} else {
throw Exception('Unexpected HTML: $node');
}
}
/// Process a list of HTML nodes.
void addAll(List<dom.Node> nodes) {
for (var node in nodes) {
add(node);
}
}
/// Escape the given character for HTML.
String escape(String char) {
if (javadocStyle) {
switch (char) {
case '<':
return '&lt;';
case '>':
return '&gt;';
case '&':
return '&amp;';
}
}
return char;
}
/// Terminate the current word and/or line, if either is in progress.
void lineBreak(bool gap) {
wordBreak();
if (line.isNotEmpty) {
writeln(line);
line = '';
}
if (gap && !atStart) {
verticalSpaceNeeded = true;
}
}
/// Insert vertical space if necessary.
void resolveVerticalSpace() {
if (verticalSpaceNeeded) {
writeln();
verticalSpaceNeeded = false;
}
}
/// Terminate the current word, if a word is in progress.
void wordBreak() {
if (word.isNotEmpty) {
atStart = false;
if (line.isNotEmpty) {
if (indentWidth + line.length + 1 + word.length <= width) {
line += ' $word';
} else {
writeln(line);
line = word;
}
} else {
line = word;
}
word = '';
}
}
}