From 6121c539fa1567c37dab786799736b2950efab66 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 14:49:49 +0900 Subject: [PATCH 1/7] Add api description convert script Converts a description written in doxygen format to dartdoc format. ex) dart run ./scripts/convert_description.dart lib/src/bindings/6.0/generated_bindings.dart before ```dart /// @brief Gets the last error code in the thread. /// @since_tizen @if MOBILE 2.3 @elseif WEARABLE 2.3.1 @endif /// @details This function retrieves the last error code which is set by set_last_result() /// @return One of #tizen_error_e /// @see #tizen_error_e /// @see set_last_result() int get_last_result() { return _get_last_result(); } ``` after ```dart /// Gets the last error code in the thread. /// /// This function retrieves the last error code which is set by set_last_result() /// /// **Since Tizen:** /// - Mobile 2.3; Wearable 2.3.1 /// /// **Returns:** /// - One of `tizen_error_e` /// /// **See also:** /// - `tizen_error_e` /// - `set_last_result()` int get_last_result() { return _get_last_result(); } ``` --- scripts/convert_description.dart | 840 +++++++++++++++++++++++++++++++ 1 file changed, 840 insertions(+) create mode 100644 scripts/convert_description.dart diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart new file mode 100644 index 00000000..6cd68b9c --- /dev/null +++ b/scripts/convert_description.dart @@ -0,0 +1,840 @@ +import 'dart:collection'; +import 'dart:io'; + +class _DocItem { + _DocItem({ + this.name, + this.direction, + required this.description, + }); + + final String? name; + final String? direction; + String description; + + void append(String text) { + description = _mergeDocText(description, text); + } +} + +class _CodeBlock { + _CodeBlock({ + required this.lines, + this.title, + }); + + final List lines; + final String? title; + final String language = 'c'; +} + +class _StructuredDoc { + final List deprecations = []; + final List summary = []; + final List details = []; + final List since = []; + final List privilegeLevels = []; + final List privileges = []; + final List remarks = []; + final List<_DocItem> parameters = []; + final List returns = []; + final List<_DocItem> returnValues = []; + final List<_DocItem> exceptions = []; + final List preconditions = []; + final List postconditions = []; + final List notes = []; + final List warnings = []; + final List seeAlso = []; + final List<_CodeBlock> codeBlocks = []; + final LinkedHashMap> extraSections = LinkedHashMap(); + + void addExtra(String label, String text) { + extraSections.putIfAbsent(label, () => []).add(text); + } +} + +void main(List args) { + if (args.isEmpty || args.contains('--help') || args.contains('-h')) { + stdout.writeln( + 'Usage: dart run tool/convert_doxygen_to_dartdoc.dart ' + '[more-dart-files...]', + ); + exit(args.isEmpty ? 64 : 0); + } + + var changedCount = 0; + for (final path in args) { + if (path.startsWith('-')) { + stderr.writeln('Unknown option: $path'); + exitCode = 64; + return; + } + if (!path.endsWith('.dart')) { + stderr.writeln('Expected a Dart file path, got: $path'); + exitCode = 64; + return; + } + + try { + final changed = convertDoxygenCommentsInDartFile(path); + if (changed) { + stdout.writeln('Converted: $path'); + changedCount++; + } else { + stdout.writeln('No changes: $path'); + } + } on Object catch (error) { + stderr.writeln(error); + exitCode = 1; + return; + } + } + + if (changedCount == 0) { + stdout.writeln('No doxygen-style doc comments were rewritten.'); + } +} + +List convertDoxygenDocCommentLines(List docLines) { + return _convertMethodDocLines(docLines); +} + +bool looksLikeDoxygenDocCommentBlock(List docLines) { + const doxygenTagPattern = + r'^@(brief|details|deprecated|since(?:_tizen)?|privlevel|privilege|' + r'remarks?|param(?:\[[^\]]+\])?|return|retval|exception|pre|post|' + r'note|warning|see|par|code|endcode|feature|platform|partner|' + r'internal|WEARABLE_ONLY|section|ingroup|addtogroup|typedef|struct|' + r'enum)\b'; + final tagRegExp = RegExp(doxygenTagPattern); + final inlineRegExp = RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); + + for (final line in docLines) { + final stripped = _stripGeneratedDocLine(line).trim(); + if (tagRegExp.hasMatch(stripped) || inlineRegExp.hasMatch(stripped)) { + return true; + } + } + + return false; +} + +String convertDoxygenCommentsInDartSource(String source) { + final newline = source.contains('\r\n') ? '\r\n' : '\n'; + final hasTrailingNewline = source.endsWith('\n'); + final lines = source.split(RegExp(r'\r?\n')).toList(); + if (hasTrailingNewline && lines.isNotEmpty && lines.last.isEmpty) { + lines.removeLast(); + } + + final output = []; + var index = 0; + while (index < lines.length) { + final line = lines[index]; + final docMatch = RegExp(r'^(\s*)///').firstMatch(line); + if (docMatch == null) { + output.add(line); + index++; + continue; + } + + final indent = docMatch.group(1)!; + final block = []; + while (index < lines.length && RegExp(r'^\s*///').hasMatch(lines[index])) { + block.add(lines[index]); + index++; + } + + final relativeBlock = block + .map((commentLine) => commentLine.substring(indent.length)) + .toList(); + if (!looksLikeDoxygenDocCommentBlock(relativeBlock)) { + output.addAll(block); + continue; + } + + final converted = convertDoxygenDocCommentLines(relativeBlock); + output.addAll(converted.map((commentLine) => '$indent$commentLine')); + } + + final convertedSource = output.join(newline); + if (hasTrailingNewline) { + return '$convertedSource$newline'; + } + return convertedSource; +} + +bool convertDoxygenCommentsInDartFile(String path) { + final file = File(path); + if (!file.existsSync()) { + throw StateError('Missing file: $path'); + } + + final original = file.readAsStringSync(); + final converted = convertDoxygenCommentsInDartSource(original); + if (converted == original) { + return false; + } + + file.writeAsStringSync(converted); + return true; +} + +List _convertMethodDocLines(List docLines) { + if (docLines.isEmpty) { + return const []; + } + + final doc = _parseStructuredDoc(docLines); + final output = []; + + void addParagraphs(List paragraphs) { + for (final paragraph in paragraphs) { + _appendDocParagraph(output, paragraph); + } + } + + void addBullets(String heading, List items) { + _appendDocBullets(output, heading, items); + } + + void addNamedBullets(String heading, List<_DocItem> items) { + _appendDocNamedBullets(output, heading, items); + } + + addParagraphs(doc.deprecations); + addParagraphs(doc.summary); + addParagraphs(doc.details); + addBullets('Since Tizen', doc.since); + addBullets('Privilege level', doc.privilegeLevels); + addBullets('Privileges', doc.privileges); + addBullets('Remarks', doc.remarks); + addNamedBullets('Parameters', doc.parameters); + addBullets('Returns', doc.returns); + addNamedBullets('Return values', doc.returnValues); + addNamedBullets('Exceptions', doc.exceptions); + addBullets('Preconditions', doc.preconditions); + addBullets('Postconditions', doc.postconditions); + addBullets('Notes', doc.notes); + addBullets('Warnings', doc.warnings); + addBullets('See also', doc.seeAlso); + + for (final entry in doc.extraSections.entries) { + addBullets(entry.key, entry.value); + } + + for (final codeBlock in doc.codeBlocks) { + if (codeBlock.title != null && codeBlock.title!.isNotEmpty) { + final title = codeBlock.title!.replaceFirst(RegExp(r':+$'), ''); + _appendDocParagraph(output, '**$title:**'); + } + _appendCodeBlock(output, codeBlock); + } + + if (output.isNotEmpty && output.last == '///') { + output.removeLast(); + } + + return output; +} + +_StructuredDoc _parseStructuredDoc(List docLines) { + final doc = _StructuredDoc(); + void Function(String text)? appendContinuation; + final pendingParagraphTitles = []; + List? currentCodeBlock; + + void addParagraph( + List target, + String text, { + bool appendToCurrentItem = true, + }) { + final normalized = _normalizeInlineDocText(text); + if (normalized.isEmpty) { + return; + } + target.add(normalized); + appendContinuation = (more) { + final nextText = _normalizeInlineDocText(more); + if (nextText.isEmpty) { + return; + } + if (appendToCurrentItem) { + target[target.length - 1] = _mergeDocText( + target[target.length - 1], + nextText, + ); + return; + } + target.add(nextText); + }; + } + + void addNamedItem( + List<_DocItem> target, + String? name, + String description, { + String? direction, + }) { + final normalized = _normalizeInlineDocText(description); + target.add( + _DocItem( + name: name == null || name.isEmpty ? null : name, + direction: direction == null || direction.isEmpty ? null : direction, + description: normalized, + ), + ); + appendContinuation = (more) { + target[target.length - 1].append(_normalizeInlineDocText(more)); + }; + } + + void addExtra(String label, String text) { + final normalized = _normalizeInlineDocText(text); + if (normalized.isEmpty) { + return; + } + doc.addExtra(label, normalized); + appendContinuation = (more) { + final items = doc.extraSections[label]!; + items[items.length - 1] = _mergeDocText( + items[items.length - 1], + _normalizeInlineDocText(more), + ); + }; + } + + void flushPendingParagraphTitles() { + while (pendingParagraphTitles.isNotEmpty) { + addExtra('Paragraph', pendingParagraphTitles.removeAt(0)); + } + } + + for (final rawDocLine in docLines) { + final stripped = _stripGeneratedDocLine(rawDocLine); + final expandedLines = _expandDocLine(stripped); + + for (final expandedLine in expandedLines) { + final line = + currentCodeBlock == null ? expandedLine.trimRight() : expandedLine; + final trimmed = line.trim(); + + if (currentCodeBlock != null) { + if (trimmed == r'@endcode' || trimmed == r'\endcode') { + final title = pendingParagraphTitles.isNotEmpty + ? pendingParagraphTitles.removeLast() + : null; + doc.codeBlocks.add( + _CodeBlock( + lines: List.from(currentCodeBlock), + title: title, + ), + ); + currentCodeBlock = null; + appendContinuation = null; + continue; + } + currentCodeBlock.add(_normalizeCodeLine(line)); + continue; + } + + if (trimmed.isEmpty) { + appendContinuation = null; + continue; + } + + if (trimmed == r'@code' || + trimmed.startsWith('@code ') || + trimmed == r'\code' || + trimmed.startsWith(r'\code ')) { + currentCodeBlock = []; + appendContinuation = null; + continue; + } + + final briefMatch = RegExp(r'^@brief\s*(.*)$').firstMatch(trimmed); + if (briefMatch != null) { + addParagraph(doc.summary, briefMatch.group(1)!); + continue; + } + + final detailsMatch = RegExp(r'^@details\s*(.*)$').firstMatch(trimmed); + if (detailsMatch != null) { + addParagraph(doc.details, detailsMatch.group(1)!); + continue; + } + + final deprecatedMatch = + RegExp(r'^@deprecated\s*(.*)$').firstMatch(trimmed); + if (deprecatedMatch != null) { + final text = deprecatedMatch.group(1)!.trim(); + addParagraph( + doc.deprecations, + text.isEmpty + ? '**Deprecated.**' + : '**Deprecated:** ${_normalizeConditionalInlineText(text)}', + ); + continue; + } + + final sinceTizenMatch = + RegExp(r'^@since_tizen\s*(.*)$').firstMatch(trimmed); + if (sinceTizenMatch != null) { + addParagraph(doc.since, _formatSinceText(sinceTizenMatch.group(1)!)); + continue; + } + + final sinceMatch = RegExp(r'^@since\s*(.*)$').firstMatch(trimmed); + if (sinceMatch != null) { + addParagraph(doc.since, sinceMatch.group(1)!); + continue; + } + + final privlevelMatch = RegExp(r'^@privlevel\s*(.*)$').firstMatch(trimmed); + if (privlevelMatch != null) { + addParagraph(doc.privilegeLevels, privlevelMatch.group(1)!); + continue; + } + + final privilegeMatch = RegExp(r'^@privilege\s*(.*)$').firstMatch(trimmed); + if (privilegeMatch != null) { + addParagraph( + doc.privileges, + privilegeMatch.group(1)!, + appendToCurrentItem: false, + ); + continue; + } + + final remarksMatch = RegExp(r'^@remarks?\s*(.*)$').firstMatch(trimmed); + if (remarksMatch != null) { + addParagraph( + doc.remarks, + remarksMatch.group(1)!, + appendToCurrentItem: false, + ); + continue; + } + + final paramMatch = RegExp( + r'^@param(?:\[(.*?)\])?\s+(\S+)\s*(.*)$', + ).firstMatch(trimmed); + if (paramMatch != null) { + addNamedItem( + doc.parameters, + paramMatch.group(2), + paramMatch.group(3) ?? '', + direction: paramMatch.group(1), + ); + continue; + } + + final returnMatch = RegExp(r'^@return\s*(.*)$').firstMatch(trimmed); + if (returnMatch != null) { + addParagraph(doc.returns, returnMatch.group(1)!); + continue; + } + + final retvalMatch = RegExp(r'^@retval\s*(.*)$').firstMatch(trimmed); + if (retvalMatch != null) { + final parsed = _parseNamedDocItem(retvalMatch.group(1)!); + addNamedItem(doc.returnValues, parsed.name, parsed.description); + continue; + } + + final exceptionMatch = RegExp(r'^@exception\s*(.*)$').firstMatch(trimmed); + if (exceptionMatch != null) { + final parsed = _parseNamedDocItem(exceptionMatch.group(1)!); + addNamedItem(doc.exceptions, parsed.name, parsed.description); + continue; + } + + final preMatch = RegExp(r'^@pre\s*(.*)$').firstMatch(trimmed); + if (preMatch != null) { + addParagraph(doc.preconditions, preMatch.group(1)!); + continue; + } + + final postMatch = RegExp(r'^@post\s*(.*)$').firstMatch(trimmed); + if (postMatch != null) { + addParagraph(doc.postconditions, postMatch.group(1)!); + continue; + } + + final noteMatch = RegExp(r'^@note\s*(.*)$').firstMatch(trimmed); + if (noteMatch != null) { + addParagraph(doc.notes, noteMatch.group(1)!); + continue; + } + + final warningMatch = RegExp(r'^@warning\s*(.*)$').firstMatch(trimmed); + if (warningMatch != null) { + addParagraph(doc.warnings, warningMatch.group(1)!); + continue; + } + + final seeMatch = RegExp(r'^@see\s*(.*)$').firstMatch(trimmed); + if (seeMatch != null) { + addParagraph(doc.seeAlso, _normalizeSeeAlsoText(seeMatch.group(1)!)); + continue; + } + + final parMatch = RegExp(r'^@par\s*(.*)$').firstMatch(trimmed); + if (parMatch != null) { + final title = _normalizeInlineDocText(parMatch.group(1)!); + if (title.isNotEmpty) { + pendingParagraphTitles.add(title); + } + appendContinuation = null; + continue; + } + + final featureMatch = RegExp(r'^@feature\s*(.*)$').firstMatch(trimmed); + if (featureMatch != null) { + addExtra('Required feature', featureMatch.group(1)!); + continue; + } + + final platformMatch = RegExp(r'^@platform\s*(.*)$').firstMatch(trimmed); + if (platformMatch != null) { + final text = platformMatch.group(1)!.trim(); + addExtra('Platform', text.isEmpty ? 'Platform API.' : text); + continue; + } + + final partnerMatch = RegExp(r'^@partner\s*(.*)$').firstMatch(trimmed); + if (partnerMatch != null) { + addExtra('Partner', partnerMatch.group(1)!); + continue; + } + + final internalMatch = RegExp(r'^@internal\s*(.*)$').firstMatch(trimmed); + if (internalMatch != null) { + addExtra('Internal', internalMatch.group(1)!); + continue; + } + + final wearableOnlyMatch = + RegExp(r'^@WEARABLE_ONLY\s*(.*)$').firstMatch(trimmed); + if (wearableOnlyMatch != null) { + final text = wearableOnlyMatch.group(1)!.trim(); + addExtra( + 'Platform restriction', + text.isEmpty ? 'Wearable only.' : text, + ); + continue; + } + + final sectionMatch = RegExp(r'^@section\s*(.*)$').firstMatch(trimmed); + if (sectionMatch != null) { + addExtra('Section', sectionMatch.group(1)!); + continue; + } + + final groupMatch = + RegExp(r'^@(?:ingroup|addtogroup)\s*(.*)$').firstMatch(trimmed); + if (groupMatch != null) { + addExtra('Group', groupMatch.group(1)!); + continue; + } + + final miscTagMatch = + RegExp(r'^@(typedef|struct|enum)\s*(.*)$').firstMatch(trimmed); + if (miscTagMatch != null) { + addExtra( + _uppercaseFirst(miscTagMatch.group(1)!), + miscTagMatch.group(2)!, + ); + continue; + } + + if (appendContinuation != null) { + appendContinuation!(trimmed); + continue; + } + + if (pendingParagraphTitles.isNotEmpty) { + addExtra(pendingParagraphTitles.removeAt(0), trimmed); + continue; + } + + addParagraph( + doc.summary.isEmpty && doc.details.isEmpty ? doc.summary : doc.details, + trimmed, + ); + } + } + + if (currentCodeBlock != null) { + doc.codeBlocks.add( + _CodeBlock( + lines: List.from(currentCodeBlock), + title: pendingParagraphTitles.isNotEmpty + ? pendingParagraphTitles.removeLast() + : null, + ), + ); + } + + flushPendingParagraphTitles(); + return doc; +} + +_DocItem _parseNamedDocItem(String text) { + final normalized = _normalizeInlineDocText(text); + final match = RegExp(r'^(\S+)\s*(.*)$').firstMatch(normalized); + if (match == null) { + return _DocItem(description: normalized); + } + + return _DocItem( + name: match.group(1), + description: (match.group(2) ?? '').trim(), + ); +} + +void _appendDocParagraph(List output, String paragraph) { + if (paragraph.trim().isEmpty) { + return; + } + if (output.isNotEmpty) { + output.add('///'); + } + for (final line in paragraph.split('\n')) { + output.add(line.isEmpty ? '///' : '/// $line'); + } +} + +void _appendDocBullets( + List output, + String heading, + List items, +) { + final filteredItems = items.where((item) => item.trim().isNotEmpty).toList(); + if (filteredItems.isEmpty) { + return; + } + if (output.isNotEmpty) { + output.add('///'); + } + output.add('/// **$heading:**'); + for (final item in filteredItems) { + final bulletText = item.startsWith('http://') || item.startsWith('https://') + ? '<$item>' + : item; + output.add('/// - $bulletText'); + } +} + +void _appendDocNamedBullets( + List output, + String heading, + List<_DocItem> items, +) { + final filteredItems = + items.where((item) => item.description.trim().isNotEmpty).toList(); + if (filteredItems.isEmpty) { + return; + } + if (output.isNotEmpty) { + output.add('///'); + } + output.add('/// **$heading:**'); + for (final item in filteredItems) { + final buffer = StringBuffer('/// - '); + if (item.name != null) { + final displayName = item.name!.startsWith('`') && item.name!.endsWith('`') + ? item.name! + : '`${item.name}`'; + buffer.write(displayName); + if (item.direction != null && item.direction!.isNotEmpty) { + buffer.write(' (${item.direction})'); + } + if (item.description.isNotEmpty) { + buffer.write(': ${item.description}'); + } + } else { + buffer.write(item.description); + } + output.add(buffer.toString()); + } +} + +void _appendCodeBlock(List output, _CodeBlock codeBlock) { + if (codeBlock.lines.isEmpty) { + return; + } + if (output.isNotEmpty) { + output.add('///'); + } + output.add('/// ```${codeBlock.language}'); + for (final line in codeBlock.lines) { + output.add(line.isEmpty ? '///' : '/// $line'); + } + output.add('/// ```'); +} + +String _stripGeneratedDocLine(String line) { + final stripped = line.startsWith('///') ? line.substring(3) : line; + return stripped.startsWith(' ') ? stripped.substring(1) : stripped; +} + +List _expandDocLine(String line) { + final expanded = + line.replaceAll(r'\n', '\n').replaceAll(RegExp(r'(?[]; + final conditionalPattern = RegExp( + r'@(?:if|elseif)\s+([A-Z_]+)\s+(.+?)(?=\s+@(?:if|elseif|else|endif)\b|$)', + ); + for (final match in conditionalPattern.allMatches(trimmed)) { + final platform = _formatPlatformLabel(match.group(1)!); + final version = _normalizeInlineDocText(match.group(2)!); + items.add('$platform $version'); + } + + final elseMatch = RegExp( + r'@else\s+(.+?)(?=\s+@endif\b|$)', + ).firstMatch(trimmed); + if (elseMatch != null) { + items.add('Otherwise ${_normalizeInlineDocText(elseMatch.group(1)!)}'); + } + + if (items.isNotEmpty) { + return items.join('; '); + } + + return _normalizeInlineDocText( + trimmed + .replaceAll('@if', '') + .replaceAll('@elseif', '') + .replaceAll('@else', '') + .replaceAll('@endif', ''), + ); +} + +String _formatPlatformLabel(String value) { + return value + .split('_') + .where((part) => part.isNotEmpty) + .map( + (part) => '${part[0]}${part.substring(1).toLowerCase()}', + ) + .join(' '); +} + +String _normalizeSeeAlsoText(String text) { + final normalized = _normalizeInlineDocText(text); + if (normalized.isEmpty) { + return normalized; + } + if (normalized.startsWith('http://') || normalized.startsWith('https://')) { + return '<$normalized>'; + } + if (normalized.startsWith('`') && normalized.endsWith('`')) { + return normalized; + } + if (RegExp(r'^[A-Za-z_][A-Za-z0-9_]*(?:\(\))?$').hasMatch(normalized)) { + return '`$normalized`'; + } + return normalized; +} + +String _normalizeConditionalInlineText(String text) { + final trimmed = text.trim(); + if (!trimmed.contains('@if')) { + return _normalizeInlineDocText(trimmed); + } + + final conditionalMatch = RegExp( + r'@if\s+[A-Z_]+\s+.+?@endif', + ).firstMatch(trimmed); + if (conditionalMatch == null) { + return _normalizeInlineDocText(trimmed); + } + + final replacement = _formatSinceText(conditionalMatch.group(0)!); + final replaced = trimmed.replaceRange( + conditionalMatch.start, + conditionalMatch.end, + replacement, + ); + return _normalizeInlineDocText(replaced); +} + +String _normalizeInlineDocText(String text) { + var normalized = text.trim(); + if (normalized.isEmpty) { + return normalized; + } + + normalized = normalized.replaceAll(r'\n', ' '); + normalized = normalized.replaceAll(RegExp(r'(? '`${match.group(1)}`', + ); + normalized = normalized.replaceAllMapped( + RegExp(r'@([abce])\s+([^\s]+)'), + (match) { + final marker = match.group(1)!; + final rawToken = match.group(2)!; + final tokenMatch = RegExp(r'^(.+?)([.,;:!?)]*)$').firstMatch(rawToken)!; + final token = tokenMatch.group(1)!; + final punctuation = tokenMatch.group(2)!; + final formatted = switch (marker) { + 'a' || 'c' => '`$token`', + 'b' => '**$token**', + 'e' => '*$token*', + _ => token, + }; + return '$formatted$punctuation'; + }, + ); + normalized = normalized.replaceAllMapped( + RegExp(r'#([A-Za-z_][A-Za-z0-9_]*(?:\(\))?)'), + (match) => '`${match.group(1)}`', + ); + normalized = normalized.replaceAllMapped( + RegExp(r'\[([^\[\]]+)\](?!\()'), + (match) => '`${match.group(1)}`', + ); + normalized = normalized.replaceAll(RegExp(r'\s+'), ' '); + return normalized.trim(); +} + +String _normalizeCodeLine(String line) { + return line.replaceAll(r'\n', '').trimRight(); +} + +String _mergeDocText(String current, String next) { + if (current.isEmpty) { + return next; + } + if (next.isEmpty) { + return current; + } + return '$current $next'; +} + +String _uppercaseFirst(String value) { + if (value.isEmpty) { + return value; + } + return '${value[0].toUpperCase()}${value.substring(1)}'; +} From 73ebfe249542efa4d8ba12590f58634721e37c7f Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 15:49:57 +0900 Subject: [PATCH 2/7] Apply Gemini review feedback to convert_description --- scripts/convert_description.dart | 92 ++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart index 6cd68b9c..07157e39 100644 --- a/scripts/convert_description.dart +++ b/scripts/convert_description.dart @@ -1,6 +1,45 @@ import 'dart:collection'; import 'dart:io'; +const _doxygenTags = [ + 'brief', + 'details', + 'deprecated', + 'since(?:_tizen)?', + 'privlevel', + 'privilege', + 'remarks?', + 'param(?:\\[[^\\]]+\\])?', + 'return', + 'retval', + 'exception', + 'pre', + 'post', + 'note', + 'warning', + 'see', + 'par', + 'code', + 'endcode', + 'feature', + 'platform', + 'partner', + 'internal', + 'WEARABLE_ONLY', + 'section', + 'ingroup', + 'addtogroup', + 'typedef', + 'struct', + 'enum', +]; + +final _doxygenTagRegExp = RegExp(r'^@(' '${_doxygenTags.join('|')}' r')\b'); +final _inlineDoxygenTagRegExp = + RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); +final _codeBlockStartRegExp = + RegExp(r'^[@\\]code(?:\{\.?([^}]+)\})?(?:\s+.*)?$'); + class _DocItem { _DocItem({ this.name, @@ -21,11 +60,12 @@ class _CodeBlock { _CodeBlock({ required this.lines, this.title, + this.language, }); final List lines; final String? title; - final String language = 'c'; + final String? language; } class _StructuredDoc { @@ -53,10 +93,11 @@ class _StructuredDoc { } } +/// Converts Doxygen-style `///` comments in one or more Dart files in place. void main(List args) { if (args.isEmpty || args.contains('--help') || args.contains('-h')) { stdout.writeln( - 'Usage: dart run tool/convert_doxygen_to_dartdoc.dart ' + 'Usage: dart run scripts/convert_description.dart ' '[more-dart-files...]', ); exit(args.isEmpty ? 64 : 0); @@ -95,23 +136,17 @@ void main(List args) { } } +/// Rewrites Doxygen-style `///` lines into dartdoc/Markdown comments. List convertDoxygenDocCommentLines(List docLines) { return _convertMethodDocLines(docLines); } +/// Returns true when a doc-comment block contains recognized Doxygen markers. bool looksLikeDoxygenDocCommentBlock(List docLines) { - const doxygenTagPattern = - r'^@(brief|details|deprecated|since(?:_tizen)?|privlevel|privilege|' - r'remarks?|param(?:\[[^\]]+\])?|return|retval|exception|pre|post|' - r'note|warning|see|par|code|endcode|feature|platform|partner|' - r'internal|WEARABLE_ONLY|section|ingroup|addtogroup|typedef|struct|' - r'enum)\b'; - final tagRegExp = RegExp(doxygenTagPattern); - final inlineRegExp = RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); - for (final line in docLines) { final stripped = _stripGeneratedDocLine(line).trim(); - if (tagRegExp.hasMatch(stripped) || inlineRegExp.hasMatch(stripped)) { + if (_doxygenTagRegExp.hasMatch(stripped) || + _inlineDoxygenTagRegExp.hasMatch(stripped)) { return true; } } @@ -119,6 +154,7 @@ bool looksLikeDoxygenDocCommentBlock(List docLines) { return false; } +/// Converts all recognized Doxygen-style `///` blocks in a Dart source string. String convertDoxygenCommentsInDartSource(String source) { final newline = source.contains('\r\n') ? '\r\n' : '\n'; final hasTrailingNewline = source.endsWith('\n'); @@ -164,6 +200,8 @@ String convertDoxygenCommentsInDartSource(String source) { return convertedSource; } +/// Rewrites recognized Doxygen-style `///` blocks in `path` and returns +/// whether the file changed. bool convertDoxygenCommentsInDartFile(String path) { final file = File(path); if (!file.existsSync()) { @@ -243,6 +281,7 @@ _StructuredDoc _parseStructuredDoc(List docLines) { void Function(String text)? appendContinuation; final pendingParagraphTitles = []; List? currentCodeBlock; + String? currentCodeLanguage; void addParagraph( List target, @@ -328,9 +367,11 @@ _StructuredDoc _parseStructuredDoc(List docLines) { _CodeBlock( lines: List.from(currentCodeBlock), title: title, + language: currentCodeLanguage, ), ); currentCodeBlock = null; + currentCodeLanguage = null; appendContinuation = null; continue; } @@ -343,11 +384,12 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - if (trimmed == r'@code' || - trimmed.startsWith('@code ') || - trimmed == r'\code' || - trimmed.startsWith(r'\code ')) { + final codeBlockStartMatch = _codeBlockStartRegExp.firstMatch(trimmed); + if (codeBlockStartMatch != null) { currentCodeBlock = []; + currentCodeLanguage = _normalizeCodeBlockLanguage( + codeBlockStartMatch.group(1), + ); appendContinuation = null; continue; } @@ -572,6 +614,7 @@ _StructuredDoc _parseStructuredDoc(List docLines) { title: pendingParagraphTitles.isNotEmpty ? pendingParagraphTitles.removeLast() : null, + language: currentCodeLanguage, ), ); } @@ -667,7 +710,9 @@ void _appendCodeBlock(List output, _CodeBlock codeBlock) { if (output.isNotEmpty) { output.add('///'); } - output.add('/// ```${codeBlock.language}'); + output.add( + codeBlock.language == null ? '/// ```' : '/// ```${codeBlock.language}', + ); for (final line in codeBlock.lines) { output.add(line.isEmpty ? '///' : '/// $line'); } @@ -822,6 +867,19 @@ String _normalizeCodeLine(String line) { return line.replaceAll(r'\n', '').trimRight(); } +String? _normalizeCodeBlockLanguage(String? language) { + if (language == null) { + return null; + } + + final normalized = language.trim(); + if (normalized.isEmpty) { + return null; + } + + return normalized.startsWith('.') ? normalized.substring(1) : normalized; +} + String _mergeDocText(String current, String next) { if (current.isEmpty) { return next; From 1b49da2a8b78b88fb7cd8155d16e3556f3eb0387 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 16:50:05 +0900 Subject: [PATCH 3/7] Hide generated bindings top-level docs during conversion --- scripts/convert_description.dart | 52 ++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart index 07157e39..47af1516 100644 --- a/scripts/convert_description.dart +++ b/scripts/convert_description.dart @@ -39,6 +39,9 @@ final _inlineDoxygenTagRegExp = RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); final _codeBlockStartRegExp = RegExp(r'^[@\\]code(?:\{\.?([^}]+)\})?(?:\s+.*)?$'); +final _topLevelDeclarationRegExp = + RegExp(r'^(typedef|(?:abstract|final)\s+class|class|enum)\s+'); +final _primaryNativeClassRegExp = RegExp(r'^class\s+Tizen[0-9]+Native\b'); class _DocItem { _DocItem({ @@ -155,7 +158,7 @@ bool looksLikeDoxygenDocCommentBlock(List docLines) { } /// Converts all recognized Doxygen-style `///` blocks in a Dart source string. -String convertDoxygenCommentsInDartSource(String source) { +String convertDoxygenCommentsInDartSource(String source, {String? path}) { final newline = source.contains('\r\n') ? '\r\n' : '\n'; final hasTrailingNewline = source.endsWith('\n'); final lines = source.split(RegExp(r'\r?\n')).toList(); @@ -193,7 +196,11 @@ String convertDoxygenCommentsInDartSource(String source) { output.addAll(converted.map((commentLine) => '$indent$commentLine')); } - final convertedSource = output.join(newline); + final normalizedOutput = + _shouldHideTopLevelGeneratedBindingsDeclarations(path) + ? _annotateGeneratedBindingsTopLevelDeclarations(output) + : output; + final convertedSource = normalizedOutput.join(newline); if (hasTrailingNewline) { return '$convertedSource$newline'; } @@ -209,7 +216,7 @@ bool convertDoxygenCommentsInDartFile(String path) { } final original = file.readAsStringSync(); - final converted = convertDoxygenCommentsInDartSource(original); + final converted = convertDoxygenCommentsInDartSource(original, path: path); if (converted == original) { return false; } @@ -218,6 +225,45 @@ bool convertDoxygenCommentsInDartFile(String path) { return true; } +bool _shouldHideTopLevelGeneratedBindingsDeclarations(String? path) { + if (path == null) { + return false; + } + + final fileName = path.split(RegExp(r'[\\/]')).last; + return fileName == 'generated_bindings.dart'; +} + +List _annotateGeneratedBindingsTopLevelDeclarations( + List lines) { + final output = []; + var braceDepth = 0; + + for (final line in lines) { + final trimmed = line.trimLeft(); + final isCommentLine = trimmed.startsWith('//'); + + if (braceDepth == 0 && + _topLevelDeclarationRegExp.hasMatch(trimmed) && + !_primaryNativeClassRegExp.hasMatch(trimmed)) { + if (output.isEmpty || output.last.trim() != '/// {@nodoc}') { + output.add('/// {@nodoc}'); + } + } + + output.add(line); + + if (isCommentLine) { + continue; + } + + braceDepth += '{'.allMatches(line).length; + braceDepth -= '}'.allMatches(line).length; + } + + return output; +} + List _convertMethodDocLines(List docLines) { if (docLines.isEmpty) { return const []; From 60353ac8685be778ffd18972647c691979d64a47 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 16:58:32 +0900 Subject: [PATCH 4/7] Hide versioned tizen getters during conversion --- scripts/convert_description.dart | 41 +++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart index 47af1516..1c791e8d 100644 --- a/scripts/convert_description.dart +++ b/scripts/convert_description.dart @@ -42,6 +42,8 @@ final _codeBlockStartRegExp = final _topLevelDeclarationRegExp = RegExp(r'^(typedef|(?:abstract|final)\s+class|class|enum)\s+'); final _primaryNativeClassRegExp = RegExp(r'^class\s+Tizen[0-9]+Native\b'); +final _publicTopLevelGetterRegExp = + RegExp(r'^[A-Za-z][A-Za-z0-9_<>,?. ]+\s+get\s+[A-Za-z][A-Za-z0-9_]*\b'); class _DocItem { _DocItem({ @@ -199,7 +201,9 @@ String convertDoxygenCommentsInDartSource(String source, {String? path}) { final normalizedOutput = _shouldHideTopLevelGeneratedBindingsDeclarations(path) ? _annotateGeneratedBindingsTopLevelDeclarations(output) - : output; + : _shouldHideVersionedTizenLibraryGetters(path) + ? _annotateVersionedTizenLibraryGetters(output) + : output; final convertedSource = normalizedOutput.join(newline); if (hasTrailingNewline) { return '$convertedSource$newline'; @@ -234,6 +238,14 @@ bool _shouldHideTopLevelGeneratedBindingsDeclarations(String? path) { return fileName == 'generated_bindings.dart'; } +bool _shouldHideVersionedTizenLibraryGetters(String? path) { + if (path == null) { + return false; + } + + return RegExp(r'(^|[\\/])lib[\\/]\d+\.\d+[\\/]tizen\.dart$').hasMatch(path); +} + List _annotateGeneratedBindingsTopLevelDeclarations( List lines) { final output = []; @@ -264,6 +276,33 @@ List _annotateGeneratedBindingsTopLevelDeclarations( return output; } +List _annotateVersionedTizenLibraryGetters(List lines) { + final output = []; + var braceDepth = 0; + + for (final line in lines) { + final trimmed = line.trimLeft(); + final isCommentLine = trimmed.startsWith('//'); + + if (braceDepth == 0 && _publicTopLevelGetterRegExp.hasMatch(trimmed)) { + if (output.isEmpty || output.last.trim() != '/// {@nodoc}') { + output.add('/// {@nodoc}'); + } + } + + output.add(line); + + if (isCommentLine) { + continue; + } + + braceDepth += '{'.allMatches(line).length; + braceDepth -= '}'.allMatches(line).length; + } + + return output; +} + List _convertMethodDocLines(List docLines) { if (docLines.isEmpty) { return const []; From 87056e2c12990095f3689e09648f302ac770bae5 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 17:16:23 +0900 Subject: [PATCH 5/7] Avoid dartdoc collisions in converted versioned libraries --- scripts/convert_description.dart | 34 +++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart index 1c791e8d..57fa2f89 100644 --- a/scripts/convert_description.dart +++ b/scripts/convert_description.dart @@ -39,6 +39,8 @@ final _inlineDoxygenTagRegExp = RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); final _codeBlockStartRegExp = RegExp(r'^[@\\]code(?:\{\.?([^}]+)\})?(?:\s+.*)?$'); +final _versionedTizenLibraryPathRegExp = + RegExp(r'(^|[\\/])lib[\\/](\d+\.\d+)[\\/]tizen\.dart$'); final _topLevelDeclarationRegExp = RegExp(r'^(typedef|(?:abstract|final)\s+class|class|enum)\s+'); final _primaryNativeClassRegExp = RegExp(r'^class\s+Tizen[0-9]+Native\b'); @@ -198,12 +200,13 @@ String convertDoxygenCommentsInDartSource(String source, {String? path}) { output.addAll(converted.map((commentLine) => '$indent$commentLine')); } + final annotatedOutput = _shouldHideTopLevelGeneratedBindingsDeclarations(path) + ? _annotateGeneratedBindingsTopLevelDeclarations(output) + : _shouldHideVersionedTizenLibraryGetters(path) + ? _annotateVersionedTizenLibraryGetters(output) + : output; final normalizedOutput = - _shouldHideTopLevelGeneratedBindingsDeclarations(path) - ? _annotateGeneratedBindingsTopLevelDeclarations(output) - : _shouldHideVersionedTizenLibraryGetters(path) - ? _annotateVersionedTizenLibraryGetters(output) - : output; + _rewriteVersionedTizenLibraryName(annotatedOutput, path); final convertedSource = normalizedOutput.join(newline); if (hasTrailingNewline) { return '$convertedSource$newline'; @@ -303,6 +306,27 @@ List _annotateVersionedTizenLibraryGetters(List lines) { return output; } +List _rewriteVersionedTizenLibraryName( + List lines, String? path) { + if (path == null) { + return lines; + } + + final match = _versionedTizenLibraryPathRegExp.firstMatch(path); + if (match == null) { + return lines; + } + + final versionId = match.group(2)!.replaceAll('.', '_'); + return lines + .map( + (line) => RegExp(r'^library\s+tizen_interop\s*;$').hasMatch(line) + ? 'library tizen_interop_$versionId;' + : line, + ) + .toList(); +} + List _convertMethodDocLines(List docLines) { if (docLines.isEmpty) { return const []; From 40649d674c3e8b85034b6e6c68b78b198fc8557a Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 18:24:15 +0900 Subject: [PATCH 6/7] Make versioned Dart libraries unique for dartdoc --- lib/10.0/tizen.dart | 3 +-- lib/6.0/tizen.dart | 2 +- lib/6.5/tizen.dart | 2 +- lib/7.0/tizen.dart | 2 +- lib/8.0/tizen.dart | 2 +- lib/9.0/tizen.dart | 2 +- 6 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/10.0/tizen.dart b/lib/10.0/tizen.dart index 2c64f1e8..5746d5af 100644 --- a/lib/10.0/tizen.dart +++ b/lib/10.0/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_10_0; import 'dart:ffi'; @@ -427,4 +427,3 @@ Tizen100Native get tizenWifiDirect => Tizen100Native? _tizenYaca; Tizen100Native get tizenYaca => _tizenYaca ??= _getTizenNative(yacaSymbols); - diff --git a/lib/6.0/tizen.dart b/lib/6.0/tizen.dart index aba59c8a..fc8cd08c 100644 --- a/lib/6.0/tizen.dart +++ b/lib/6.0/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_6_0; import 'dart:ffi'; diff --git a/lib/6.5/tizen.dart b/lib/6.5/tizen.dart index 3714f7b4..6cf3385c 100644 --- a/lib/6.5/tizen.dart +++ b/lib/6.5/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_6_5; import 'dart:ffi'; diff --git a/lib/7.0/tizen.dart b/lib/7.0/tizen.dart index ae0060d8..b6263aa4 100644 --- a/lib/7.0/tizen.dart +++ b/lib/7.0/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_7_0; import 'dart:ffi'; diff --git a/lib/8.0/tizen.dart b/lib/8.0/tizen.dart index fe79d3e7..61276f82 100644 --- a/lib/8.0/tizen.dart +++ b/lib/8.0/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_8_0; import 'dart:ffi'; diff --git a/lib/9.0/tizen.dart b/lib/9.0/tizen.dart index c08b7afe..d4588987 100644 --- a/lib/9.0/tizen.dart +++ b/lib/9.0/tizen.dart @@ -1,4 +1,4 @@ -library tizen_interop; +library tizen_interop_9_0; import 'dart:ffi'; From 79bf60f43f7a43c7be2bfe8b1604ab0a0c61ec17 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 20 Mar 2026 19:46:39 +0900 Subject: [PATCH 7/7] Apply latest Gemini performance feedback --- scripts/convert_description.dart | 178 +++++++++++++++++-------------- 1 file changed, 98 insertions(+), 80 deletions(-) diff --git a/scripts/convert_description.dart b/scripts/convert_description.dart index 57fa2f89..0e016833 100644 --- a/scripts/convert_description.dart +++ b/scripts/convert_description.dart @@ -37,15 +37,60 @@ const _doxygenTags = [ final _doxygenTagRegExp = RegExp(r'^@(' '${_doxygenTags.join('|')}' r')\b'); final _inlineDoxygenTagRegExp = RegExp(r'@(?:if|elseif|else|endif|ref|[abce])\b'); +final _docCommentStartRegExp = RegExp(r'^(\s*)///'); +final _docCommentLineRegExp = RegExp(r'^\s*///'); final _codeBlockStartRegExp = RegExp(r'^[@\\]code(?:\{\.?([^}]+)\})?(?:\s+.*)?$'); final _versionedTizenLibraryPathRegExp = RegExp(r'(^|[\\/])lib[\\/](\d+\.\d+)[\\/]tizen\.dart$'); +final _pathSeparatorRegExp = RegExp(r'[\\/]'); final _topLevelDeclarationRegExp = RegExp(r'^(typedef|(?:abstract|final)\s+class|class|enum)\s+'); final _primaryNativeClassRegExp = RegExp(r'^class\s+Tizen[0-9]+Native\b'); final _publicTopLevelGetterRegExp = RegExp(r'^[A-Za-z][A-Za-z0-9_<>,?. ]+\s+get\s+[A-Za-z][A-Za-z0-9_]*\b'); +final _libraryTizenInteropRegExp = RegExp(r'^library\s+tizen_interop\s*;$'); +final _briefTagRegExp = RegExp(r'^@brief\s*(.*)$'); +final _detailsTagRegExp = RegExp(r'^@details\s*(.*)$'); +final _deprecatedTagRegExp = RegExp(r'^@deprecated\s*(.*)$'); +final _sinceTizenTagRegExp = RegExp(r'^@since_tizen\s*(.*)$'); +final _sinceTagRegExp = RegExp(r'^@since\s*(.*)$'); +final _privlevelTagRegExp = RegExp(r'^@privlevel\s*(.*)$'); +final _privilegeTagRegExp = RegExp(r'^@privilege\s*(.*)$'); +final _remarksTagRegExp = RegExp(r'^@remarks?\s*(.*)$'); +final _paramTagRegExp = RegExp(r'^@param(?:\[(.*?)\])?\s+(\S+)\s*(.*)$'); +final _returnTagRegExp = RegExp(r'^@return\s*(.*)$'); +final _retvalTagRegExp = RegExp(r'^@retval\s*(.*)$'); +final _exceptionTagRegExp = RegExp(r'^@exception\s*(.*)$'); +final _preTagRegExp = RegExp(r'^@pre\s*(.*)$'); +final _postTagRegExp = RegExp(r'^@post\s*(.*)$'); +final _noteTagRegExp = RegExp(r'^@note\s*(.*)$'); +final _warningTagRegExp = RegExp(r'^@warning\s*(.*)$'); +final _seeTagRegExp = RegExp(r'^@see\s*(.*)$'); +final _parTagRegExp = RegExp(r'^@par\s*(.*)$'); +final _featureTagRegExp = RegExp(r'^@feature\s*(.*)$'); +final _platformTagRegExp = RegExp(r'^@platform\s*(.*)$'); +final _partnerTagRegExp = RegExp(r'^@partner\s*(.*)$'); +final _internalTagRegExp = RegExp(r'^@internal\s*(.*)$'); +final _wearableOnlyTagRegExp = RegExp(r'^@WEARABLE_ONLY\s*(.*)$'); +final _sectionTagRegExp = RegExp(r'^@section\s*(.*)$'); +final _groupTagRegExp = RegExp(r'^@(?:ingroup|addtogroup)\s*(.*)$'); +final _miscTagRegExp = RegExp(r'^@(typedef|struct|enum)\s*(.*)$'); +final _namedDocItemRegExp = RegExp(r'^(\S+)\s*(.*)$'); +final _titleTrailingColonRegExp = RegExp(r':+$'); +final _encodedNewlineMarkerRegExp = RegExp(r'(?[]; - while (index < lines.length && RegExp(r'^\s*///').hasMatch(lines[index])) { + while ( + index < lines.length && _docCommentLineRegExp.hasMatch(lines[index])) { block.add(lines[index]); index++; } @@ -237,7 +283,7 @@ bool _shouldHideTopLevelGeneratedBindingsDeclarations(String? path) { return false; } - final fileName = path.split(RegExp(r'[\\/]')).last; + final fileName = path.split(_pathSeparatorRegExp).last; return fileName == 'generated_bindings.dart'; } @@ -246,19 +292,18 @@ bool _shouldHideVersionedTizenLibraryGetters(String? path) { return false; } - return RegExp(r'(^|[\\/])lib[\\/]\d+\.\d+[\\/]tizen\.dart$').hasMatch(path); + return _versionedTizenLibraryPathRegExp.hasMatch(path); } List _annotateGeneratedBindingsTopLevelDeclarations( List lines) { final output = []; - var braceDepth = 0; for (final line in lines) { final trimmed = line.trimLeft(); - final isCommentLine = trimmed.startsWith('//'); + final isTopLevelLine = trimmed == line; - if (braceDepth == 0 && + if (isTopLevelLine && _topLevelDeclarationRegExp.hasMatch(trimmed) && !_primaryNativeClassRegExp.hasMatch(trimmed)) { if (output.isEmpty || output.last.trim() != '/// {@nodoc}') { @@ -267,13 +312,6 @@ List _annotateGeneratedBindingsTopLevelDeclarations( } output.add(line); - - if (isCommentLine) { - continue; - } - - braceDepth += '{'.allMatches(line).length; - braceDepth -= '}'.allMatches(line).length; } return output; @@ -281,26 +319,18 @@ List _annotateGeneratedBindingsTopLevelDeclarations( List _annotateVersionedTizenLibraryGetters(List lines) { final output = []; - var braceDepth = 0; for (final line in lines) { final trimmed = line.trimLeft(); - final isCommentLine = trimmed.startsWith('//'); + final isTopLevelLine = trimmed == line; - if (braceDepth == 0 && _publicTopLevelGetterRegExp.hasMatch(trimmed)) { + if (isTopLevelLine && _publicTopLevelGetterRegExp.hasMatch(trimmed)) { if (output.isEmpty || output.last.trim() != '/// {@nodoc}') { output.add('/// {@nodoc}'); } } output.add(line); - - if (isCommentLine) { - continue; - } - - braceDepth += '{'.allMatches(line).length; - braceDepth -= '}'.allMatches(line).length; } return output; @@ -320,7 +350,7 @@ List _rewriteVersionedTizenLibraryName( final versionId = match.group(2)!.replaceAll('.', '_'); return lines .map( - (line) => RegExp(r'^library\s+tizen_interop\s*;$').hasMatch(line) + (line) => _libraryTizenInteropRegExp.hasMatch(line) ? 'library tizen_interop_$versionId;' : line, ) @@ -372,7 +402,8 @@ List _convertMethodDocLines(List docLines) { for (final codeBlock in doc.codeBlocks) { if (codeBlock.title != null && codeBlock.title!.isNotEmpty) { - final title = codeBlock.title!.replaceFirst(RegExp(r':+$'), ''); + final title = + codeBlock.title!.replaceFirst(_titleTrailingColonRegExp, ''); _appendDocParagraph(output, '**$title:**'); } _appendCodeBlock(output, codeBlock); @@ -503,20 +534,19 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final briefMatch = RegExp(r'^@brief\s*(.*)$').firstMatch(trimmed); + final briefMatch = _briefTagRegExp.firstMatch(trimmed); if (briefMatch != null) { addParagraph(doc.summary, briefMatch.group(1)!); continue; } - final detailsMatch = RegExp(r'^@details\s*(.*)$').firstMatch(trimmed); + final detailsMatch = _detailsTagRegExp.firstMatch(trimmed); if (detailsMatch != null) { addParagraph(doc.details, detailsMatch.group(1)!); continue; } - final deprecatedMatch = - RegExp(r'^@deprecated\s*(.*)$').firstMatch(trimmed); + final deprecatedMatch = _deprecatedTagRegExp.firstMatch(trimmed); if (deprecatedMatch != null) { final text = deprecatedMatch.group(1)!.trim(); addParagraph( @@ -528,26 +558,25 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final sinceTizenMatch = - RegExp(r'^@since_tizen\s*(.*)$').firstMatch(trimmed); + final sinceTizenMatch = _sinceTizenTagRegExp.firstMatch(trimmed); if (sinceTizenMatch != null) { addParagraph(doc.since, _formatSinceText(sinceTizenMatch.group(1)!)); continue; } - final sinceMatch = RegExp(r'^@since\s*(.*)$').firstMatch(trimmed); + final sinceMatch = _sinceTagRegExp.firstMatch(trimmed); if (sinceMatch != null) { addParagraph(doc.since, sinceMatch.group(1)!); continue; } - final privlevelMatch = RegExp(r'^@privlevel\s*(.*)$').firstMatch(trimmed); + final privlevelMatch = _privlevelTagRegExp.firstMatch(trimmed); if (privlevelMatch != null) { addParagraph(doc.privilegeLevels, privlevelMatch.group(1)!); continue; } - final privilegeMatch = RegExp(r'^@privilege\s*(.*)$').firstMatch(trimmed); + final privilegeMatch = _privilegeTagRegExp.firstMatch(trimmed); if (privilegeMatch != null) { addParagraph( doc.privileges, @@ -557,7 +586,7 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final remarksMatch = RegExp(r'^@remarks?\s*(.*)$').firstMatch(trimmed); + final remarksMatch = _remarksTagRegExp.firstMatch(trimmed); if (remarksMatch != null) { addParagraph( doc.remarks, @@ -567,9 +596,7 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final paramMatch = RegExp( - r'^@param(?:\[(.*?)\])?\s+(\S+)\s*(.*)$', - ).firstMatch(trimmed); + final paramMatch = _paramTagRegExp.firstMatch(trimmed); if (paramMatch != null) { addNamedItem( doc.parameters, @@ -580,57 +607,57 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final returnMatch = RegExp(r'^@return\s*(.*)$').firstMatch(trimmed); + final returnMatch = _returnTagRegExp.firstMatch(trimmed); if (returnMatch != null) { addParagraph(doc.returns, returnMatch.group(1)!); continue; } - final retvalMatch = RegExp(r'^@retval\s*(.*)$').firstMatch(trimmed); + final retvalMatch = _retvalTagRegExp.firstMatch(trimmed); if (retvalMatch != null) { final parsed = _parseNamedDocItem(retvalMatch.group(1)!); addNamedItem(doc.returnValues, parsed.name, parsed.description); continue; } - final exceptionMatch = RegExp(r'^@exception\s*(.*)$').firstMatch(trimmed); + final exceptionMatch = _exceptionTagRegExp.firstMatch(trimmed); if (exceptionMatch != null) { final parsed = _parseNamedDocItem(exceptionMatch.group(1)!); addNamedItem(doc.exceptions, parsed.name, parsed.description); continue; } - final preMatch = RegExp(r'^@pre\s*(.*)$').firstMatch(trimmed); + final preMatch = _preTagRegExp.firstMatch(trimmed); if (preMatch != null) { addParagraph(doc.preconditions, preMatch.group(1)!); continue; } - final postMatch = RegExp(r'^@post\s*(.*)$').firstMatch(trimmed); + final postMatch = _postTagRegExp.firstMatch(trimmed); if (postMatch != null) { addParagraph(doc.postconditions, postMatch.group(1)!); continue; } - final noteMatch = RegExp(r'^@note\s*(.*)$').firstMatch(trimmed); + final noteMatch = _noteTagRegExp.firstMatch(trimmed); if (noteMatch != null) { addParagraph(doc.notes, noteMatch.group(1)!); continue; } - final warningMatch = RegExp(r'^@warning\s*(.*)$').firstMatch(trimmed); + final warningMatch = _warningTagRegExp.firstMatch(trimmed); if (warningMatch != null) { addParagraph(doc.warnings, warningMatch.group(1)!); continue; } - final seeMatch = RegExp(r'^@see\s*(.*)$').firstMatch(trimmed); + final seeMatch = _seeTagRegExp.firstMatch(trimmed); if (seeMatch != null) { addParagraph(doc.seeAlso, _normalizeSeeAlsoText(seeMatch.group(1)!)); continue; } - final parMatch = RegExp(r'^@par\s*(.*)$').firstMatch(trimmed); + final parMatch = _parTagRegExp.firstMatch(trimmed); if (parMatch != null) { final title = _normalizeInlineDocText(parMatch.group(1)!); if (title.isNotEmpty) { @@ -640,33 +667,32 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final featureMatch = RegExp(r'^@feature\s*(.*)$').firstMatch(trimmed); + final featureMatch = _featureTagRegExp.firstMatch(trimmed); if (featureMatch != null) { addExtra('Required feature', featureMatch.group(1)!); continue; } - final platformMatch = RegExp(r'^@platform\s*(.*)$').firstMatch(trimmed); + final platformMatch = _platformTagRegExp.firstMatch(trimmed); if (platformMatch != null) { final text = platformMatch.group(1)!.trim(); addExtra('Platform', text.isEmpty ? 'Platform API.' : text); continue; } - final partnerMatch = RegExp(r'^@partner\s*(.*)$').firstMatch(trimmed); + final partnerMatch = _partnerTagRegExp.firstMatch(trimmed); if (partnerMatch != null) { addExtra('Partner', partnerMatch.group(1)!); continue; } - final internalMatch = RegExp(r'^@internal\s*(.*)$').firstMatch(trimmed); + final internalMatch = _internalTagRegExp.firstMatch(trimmed); if (internalMatch != null) { addExtra('Internal', internalMatch.group(1)!); continue; } - final wearableOnlyMatch = - RegExp(r'^@WEARABLE_ONLY\s*(.*)$').firstMatch(trimmed); + final wearableOnlyMatch = _wearableOnlyTagRegExp.firstMatch(trimmed); if (wearableOnlyMatch != null) { final text = wearableOnlyMatch.group(1)!.trim(); addExtra( @@ -676,21 +702,19 @@ _StructuredDoc _parseStructuredDoc(List docLines) { continue; } - final sectionMatch = RegExp(r'^@section\s*(.*)$').firstMatch(trimmed); + final sectionMatch = _sectionTagRegExp.firstMatch(trimmed); if (sectionMatch != null) { addExtra('Section', sectionMatch.group(1)!); continue; } - final groupMatch = - RegExp(r'^@(?:ingroup|addtogroup)\s*(.*)$').firstMatch(trimmed); + final groupMatch = _groupTagRegExp.firstMatch(trimmed); if (groupMatch != null) { addExtra('Group', groupMatch.group(1)!); continue; } - final miscTagMatch = - RegExp(r'^@(typedef|struct|enum)\s*(.*)$').firstMatch(trimmed); + final miscTagMatch = _miscTagRegExp.firstMatch(trimmed); if (miscTagMatch != null) { addExtra( _uppercaseFirst(miscTagMatch.group(1)!), @@ -734,7 +758,7 @@ _StructuredDoc _parseStructuredDoc(List docLines) { _DocItem _parseNamedDocItem(String text) { final normalized = _normalizeInlineDocText(text); - final match = RegExp(r'^(\S+)\s*(.*)$').firstMatch(normalized); + final match = _namedDocItemRegExp.firstMatch(normalized); if (match == null) { return _DocItem(description: normalized); } @@ -834,8 +858,9 @@ String _stripGeneratedDocLine(String line) { } List _expandDocLine(String line) { - final expanded = - line.replaceAll(r'\n', '\n').replaceAll(RegExp(r'(?[]; - final conditionalPattern = RegExp( - r'@(?:if|elseif)\s+([A-Z_]+)\s+(.+?)(?=\s+@(?:if|elseif|else|endif)\b|$)', - ); - for (final match in conditionalPattern.allMatches(trimmed)) { + for (final match in _conditionalSinceBranchRegExp.allMatches(trimmed)) { final platform = _formatPlatformLabel(match.group(1)!); final version = _normalizeInlineDocText(match.group(2)!); items.add('$platform $version'); } - final elseMatch = RegExp( - r'@else\s+(.+?)(?=\s+@endif\b|$)', - ).firstMatch(trimmed); + final elseMatch = _conditionalElseRegExp.firstMatch(trimmed); if (elseMatch != null) { items.add('Otherwise ${_normalizeInlineDocText(elseMatch.group(1)!)}'); } @@ -900,7 +920,7 @@ String _normalizeSeeAlsoText(String text) { if (normalized.startsWith('`') && normalized.endsWith('`')) { return normalized; } - if (RegExp(r'^[A-Za-z_][A-Za-z0-9_]*(?:\(\))?$').hasMatch(normalized)) { + if (_simpleReferenceRegExp.hasMatch(normalized)) { return '`$normalized`'; } return normalized; @@ -912,9 +932,7 @@ String _normalizeConditionalInlineText(String text) { return _normalizeInlineDocText(trimmed); } - final conditionalMatch = RegExp( - r'@if\s+[A-Z_]+\s+.+?@endif', - ).firstMatch(trimmed); + final conditionalMatch = _conditionalInlineBlockRegExp.firstMatch(trimmed); if (conditionalMatch == null) { return _normalizeInlineDocText(trimmed); } @@ -935,20 +953,20 @@ String _normalizeInlineDocText(String text) { } normalized = normalized.replaceAll(r'\n', ' '); - normalized = normalized.replaceAll(RegExp(r'(? '`${match.group(1)}`', ); normalized = normalized.replaceAllMapped( - RegExp(r'@([abce])\s+([^\s]+)'), + _inlineFormattingTagRegExp, (match) { final marker = match.group(1)!; final rawToken = match.group(2)!; - final tokenMatch = RegExp(r'^(.+?)([.,;:!?)]*)$').firstMatch(rawToken)!; + final tokenMatch = _trailingPunctuationRegExp.firstMatch(rawToken)!; final token = tokenMatch.group(1)!; final punctuation = tokenMatch.group(2)!; final formatted = switch (marker) { @@ -961,14 +979,14 @@ String _normalizeInlineDocText(String text) { }, ); normalized = normalized.replaceAllMapped( - RegExp(r'#([A-Za-z_][A-Za-z0-9_]*(?:\(\))?)'), + _hashReferenceRegExp, (match) => '`${match.group(1)}`', ); normalized = normalized.replaceAllMapped( - RegExp(r'\[([^\[\]]+)\](?!\()'), + _squareBracketLiteralRegExp, (match) => '`${match.group(1)}`', ); - normalized = normalized.replaceAll(RegExp(r'\s+'), ' '); + normalized = normalized.replaceAll(_whitespaceRegExp, ' '); return normalized.trim(); }