blob: 7aafdb6d022393cc397af40c4e645976da58e62a [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' show LineSplitter;
import 'dart:io';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/analysis/utilities.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:pub_semver/pub_semver.dart';
/// Returns all indexed fields in [className].
///
/// Field names are expected to be of the form `kFooBarIndex`; prefixed with a
/// `k` and terminated in `Index`.
List<String> getDartClassFields({
required String sourcePath,
required String className,
}) {
final List<String> includedPaths = <String>[sourcePath];
final AnalysisContextCollection collection = AnalysisContextCollection(includedPaths: includedPaths);
final AnalysisContext context = collection.contextFor(sourcePath);
final AnalysisSession session = context.currentSession;
final SomeParsedUnitResult result = session.getParsedUnit(sourcePath);
if (result is! ParsedUnitResult) {
return <String>[];
}
// Locate all fields matching the expression in the class.
final RegExp fieldExp = RegExp(r'_k(\w*)Index');
final List<String> fields = <String>[];
for (final CompilationUnitMember unitMember in result.unit.declarations) {
if (unitMember is ClassDeclaration && unitMember.name.lexeme == className) {
for (final ClassMember classMember in unitMember.members) {
if (classMember is FieldDeclaration) {
for (final VariableDeclaration field in classMember.fields.variables) {
final String fieldName = field.name.lexeme;
final RegExpMatch? match = fieldExp.firstMatch(fieldName);
if (match != null) {
fields.add(match.group(1)!);
}
}
}
}
}
}
return fields;
}
/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kEnumNameFooBar`; prefixed with
/// `kEnumName`.
List<String> getCppEnumValues({
required String sourcePath,
required String enumName,
}) {
final List<String> lines = File(sourcePath).readAsLinesSync();
final int enumEnd = lines.indexOf('} $enumName;');
if (enumEnd < 0) {
return <String>[];
}
final int enumStart = lines.lastIndexOf('typedef enum {', enumEnd);
if (enumStart < 0 || enumStart >= enumEnd) {
return <String>[];
}
final RegExp valueExp = RegExp('^\\s*k$enumName(\\w*)');
return _extractMatchingExpression(
lines: lines.sublist(enumStart + 1, enumEnd),
regexp: valueExp,
);
}
/// Returns all values in [enumName].
///
/// Enum values are expected to be of the form `kFooBar`; prefixed with `k`.
List<String> getCppEnumClassValues({
required String sourcePath,
required String enumName,
}) {
final List<String> lines = _getBlockStartingWith(
source: File(sourcePath).readAsStringSync(),
startExp: RegExp('enum class $enumName .* {'),
);
final RegExp valueExp = RegExp(r'^\s*k(\w*)');
return _extractMatchingExpression(lines: lines, regexp: valueExp);
}
/// Returns all values in [enumName].
///
/// Enum value declarations are expected to be of the form `FOO_BAR(1 << N)`;
/// in all caps.
List<String> getJavaEnumValues({
required String sourcePath,
required String enumName,
}) {
final List<String> lines = _getBlockStartingWith(
source: File(sourcePath).readAsStringSync(),
startExp: RegExp('enum $enumName {'),
);
final RegExp valueExp = RegExp(r'^\s*([A-Z_]*)\(');
return _extractMatchingExpression(lines: lines, regexp: valueExp);
}
/// Returns all values in [lines] whose line of code matches [regexp].
///
/// The contents of the first match group in [regexp] is returned; therefore
/// it must contain a match group.
List<String> _extractMatchingExpression({
required Iterable<String> lines,
required RegExp regexp,
}) {
final List<String> values = <String>[];
for (final String line in lines) {
final RegExpMatch? match = regexp.firstMatch(line);
if (match != null) {
values.add(match.group(1)!);
}
}
return values;
}
/// Returns all lines of the block starting with [startString].
///
/// [startString] MUST end with '{'.
List<String> _getBlockStartingWith({
required String source,
required RegExp startExp,
}) {
assert(startExp.pattern.endsWith('{'));
final int blockStart = source.indexOf(startExp);
if (blockStart < 0) {
return <String>[];
}
// Find start of block.
int pos = blockStart;
while (pos < source.length && source[pos] != '{') {
pos++;
}
int braceCount = 1;
// Count braces until end of block.
pos++;
while (pos < source.length && braceCount > 0) {
if (source[pos] == '{') {
braceCount++;
} else if (source[pos] == '}') {
braceCount--;
}
pos++;
}
final int blockEnd = pos;
return LineSplitter.split(source, blockStart, blockEnd).toList();
}
/// Apply a visitor to all compilation units in the dart:ui library.
void visitUIUnits(String flutterRoot, AstVisitor<void> visitor) {
final String uiRoot = '$flutterRoot/lib/ui';
final FeatureSet analyzerFeatures = FeatureSet.fromEnableFlags2(
sdkLanguageVersion: Version.parse('2.17.0'),
flags: <String>['non-nullable'],
);
final ParseStringResult uiResult = parseFile(path: '$uiRoot/ui.dart', featureSet: analyzerFeatures);
for (final PartDirective part in uiResult.unit.directives.whereType<PartDirective>()) {
final String partPath = part.uri.stringValue!;
final ParseStringResult partResult = parseFile(path: '$uiRoot/$partPath', featureSet: analyzerFeatures);
for (final CompilationUnitMember unitMember in partResult.unit.declarations) {
unitMember.accept(visitor);
}
}
}