Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions script/tool/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

* Adds support for batch release of pre-1.0 packages.
* Runs `cognitive_complexity` on Dart files in `evals/test_data` directories as well as `lib/`.

## 0.14.4

Expand Down
14 changes: 9 additions & 5 deletions script/tool/lib/src/analyze_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -372,14 +372,18 @@ class AnalyzeCommand extends PackageLoopingCommand {
///
/// Assumes `cognitive_complexity` is present in `dev_dependencies`.
Future<List<String>> _runCognitiveComplexityForPackage(RepositoryPackage package) async {
if (!package.libDirectory.existsSync()) {
return <String>[];
}
final filesToAnalyze = <String>[];
for (final FileSystemEntity entity in package.libDirectory.listSync(recursive: true)) {
for (final FileSystemEntity entity in package.directory.listSync(recursive: true)) {
if (entity is File && entity.path.endsWith('.dart') && !_isGeneratedDartFile(entity.path)) {
final String relativePath = path.relative(entity.path, from: package.directory.path);
filesToAnalyze.add(relativePath.replaceAll(r'\', '/'));
final String posixPath = relativePath.replaceAll(r'\', '/');
final bool isLib = posixPath.startsWith('lib/');
final bool isRootEvalData = posixPath.startsWith('evals/test_data/');
final bool isSkillEvalData =
posixPath.startsWith('.agents/skills/') && posixPath.contains('/evals/test_data/');
if (isLib || isRootEvalData || isSkillEvalData) {
filesToAnalyze.add(posixPath);
}
}
}
Comment on lines 375 to 388

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Listing the entire package directory recursively (package.directory.listSync(recursive: true)) is highly inefficient and can cause significant performance bottlenecks in CI/CD pipelines, especially for large packages with many build artifacts (e.g., .dart_tool/, build/, ios/Pods/, node_modules/).

Since we only care about Dart files in lib/, evals/test_data/, and .agents/skills/, we should only recursively list those specific directories if they exist.

    final List<Directory> directoriesToSearch = <Directory>[
      package.libDirectory,
      package.directory.childDirectory('evals').childDirectory('test_data'),
      package.directory.childDirectory('.agents').childDirectory('skills'),
    ];
    final List<String> filesToAnalyze = <String>[];
    for (final Directory dir in directoriesToSearch) {
      if (!dir.existsSync()) {
        continue;
      }
      for (final FileSystemEntity entity in dir.listSync(recursive: true)) {
        if (entity is File && entity.path.endsWith('.dart') && !_isGeneratedDartFile(entity.path)) {
          final String relativePath = path.relative(entity.path, from: package.directory.path);
          final String posixPath = relativePath.replaceAll(r'\', '/');
          final bool isLib = posixPath.startsWith('lib/');
          final bool isRootEvalData = posixPath.startsWith('evals/test_data/');
          final bool isSkillEvalData = 
              posixPath.startsWith('.agents/skills/') && posixPath.contains('/evals/test_data/');
          if (isLib || isRootEvalData || isSkillEvalData) {
            filesToAnalyze.add(posixPath);
          }
        }
      }
    }

if (filesToAnalyze.isEmpty) {
Expand Down
90 changes: 89 additions & 1 deletion script/tool/test/analyze_command_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -550,14 +550,69 @@ void main() {
expect(output, contains('Running cognitive_complexity analysis...'));
});

test('skips cognitive_complexity if lib/ does not exist', () async {
test('runs cognitive_complexity on files in evals/test_data and lib', () async {
final RepositoryPackage package = createFakePackage(
'a_package',
packagesDir,
isFlutter: true,
);
_writeFakePubspecWithLinter(package, inDevDependencies: true);
package.libDirectory.childFile('lib.dart').createSync();
package.directory
.childDirectory('evals')
.childDirectory('test_data')
.childFile('data.dart')
.createSync(recursive: true);
package.directory
.childDirectory('.agents')
.childDirectory('skills')
.childDirectory('evals')
.childDirectory('test_data')
.childFile('eval.dart')
.createSync(recursive: true);

_mockCallsForFlutterAnalyze(
processRunner,
extraDartCalls: [
FakeProcessInfo(MockProcess(), <String>['run', 'cognitive_complexity']),
],
);

final List<String> output = await runCapturingPrint(runner, <String>['analyze']);

expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall('flutter', const <String>['pub', 'get'], package.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'], package.path),
ProcessCall('dart', const <String>[
'run',
'cognitive_complexity',
'.agents/skills/evals/test_data/eval.dart',
'evals/test_data/data.dart',
'lib/lib.dart',
], package.path),
]),
);
expect(output, contains('Running cognitive_complexity analysis...'));
});

test('skips cognitive_complexity if no lib/ or evals/test_data/ dart files exist', () async {
final RepositoryPackage package = createFakePackage(
'a_package',
packagesDir,
isFlutter: true,
);
_writeFakePubspecWithLinter(package, inDevDependencies: true);
package.libDirectory.deleteSync(recursive: true);
package.directory
.childDirectory('test')
.childFile('foo_test.dart')
.createSync(recursive: true);
package.directory
.childDirectory('test_data')
.childFile('fixture.dart')
.createSync(recursive: true);

_mockCallsForFlutterAnalyze(processRunner);

Expand All @@ -574,6 +629,39 @@ void main() {
expect(combinedOutput, isNot(contains('cognitive_complexity')));
});

test(
'skips cognitive_complexity if evals/test_data/ only contains generated files',
() async {
final RepositoryPackage package = createFakePackage(
'a_package',
packagesDir,
isFlutter: true,
);
_writeFakePubspecWithLinter(package, inDevDependencies: true);
package.libDirectory.deleteSync(recursive: true);
final Directory testDataDir = package.directory
.childDirectory('evals')
.childDirectory('test_data');
testDataDir.childFile('foo.g.dart').createSync(recursive: true);
testDataDir.childFile('bar.mocks.dart').createSync(recursive: true);
testDataDir.childFile('baz.gen.dart').createSync(recursive: true);

_mockCallsForFlutterAnalyze(processRunner);

final List<String> output = await runCapturingPrint(runner, <String>['analyze']);

expect(
processRunner.recordedCalls,
orderedEquals(<ProcessCall>[
ProcessCall('flutter', const <String>['pub', 'get'], package.path),
ProcessCall('dart', const <String>['analyze', '--fatal-infos'], package.path),
]),
);
final String combinedOutput = output.join('\n').toLowerCase();
expect(combinedOutput, isNot(contains('cognitive_complexity')));
},
);

test('skips cognitive_complexity if lib/ only contains generated files', () async {
final RepositoryPackage package = createFakePackage(
'a_package',
Expand Down
Loading