blob: bbefb8f854d0270fca4e93ddc47e30466f9a4cee [file] [edit]
// Copyright (c) 2025, 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.
import 'dart:io';
import 'package:analyzer/dart/element/element.dart';
import 'package:cli_util/cli_logging.dart';
import 'package:jot/api.dart';
import 'package:jot/src/analysis.dart';
import 'package:jot/src/html.dart';
import 'package:jot/src/render.dart';
import 'package:jot/workspace.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
@isTest
void testWithSource(
String description,
String source,
void Function(TestProject) body,
) {
test(description, () async {
final project = TestProject({'main.dart': source});
try {
await project.init();
body(project);
} finally {
project.dispose();
}
});
}
class TestProject {
final Directory dir;
final Map<String, String> sources;
late final Workspace workspace;
late final Analyzer analyzer;
TestProject(this.sources) : dir = Directory.systemTemp.createTempSync();
factory TestProject.fromTemplate(Directory dir) {
final libDir = Directory(p.join(dir.path, 'lib'));
final files = libDir
.listSync()
.whereType<File>()
.where((file) => file.path.endsWith('.dart'))
.toList();
return TestProject({
for (var file in files)
p.relative(file.path, from: libDir.path): file.readAsStringSync(),
});
}
String get libDirPath => p.join(dir.path, 'lib');
Directory get outDir => Directory(p.join(dir.path, 'doc', 'api'));
Resolver get resolver => workspace.api.resolver;
List<LibraryItemContainer> get libraries =>
workspace.api.packages.first.libraries;
LibraryItemContainer get firstLibrary => libraries.first;
Item get firstItem => firstLibrary.allChildren.first;
Item itemNamed(String name) =>
firstLibrary.allChildren.firstWhere((item) => item.element.name == name);
Items get firstInterfaceItem =>
firstLibrary.allChildren.firstWhere(
(item) => item.element is InterfaceElement,
)
as Items;
Items classNamed(String name) =>
firstLibrary.allChildren.firstWhere(
(item) =>
item.element is InterfaceElement && item.element.name == name,
)
as Items;
Items extensionTypeNamed(String name) =>
firstLibrary.allChildren.firstWhere(
(item) =>
// ignore: experimental_member_use
item.element is ExtensionTypeElement &&
item.element.name == name,
)
as Items;
LinkedCodeRenderer rendererFor(Item item) {
return LinkedCodeRenderer(resolver, resolver.fileFor(item.element)!);
}
void _create() {
final pubspec = File(p.join(dir.path, 'pubspec.yaml'));
pubspec.writeAsStringSync('name: testing');
Directory(libDirPath).createSync();
for (final entry in sources.entries) {
final file = File(p.join(libDirPath, entry.key));
file.writeAsStringSync(entry.value);
}
}
Future<void> init() async {
_create();
final htmlTemplate = await HtmlTemplate.createDefault();
workspace = Workspace.fromPackage(htmlTemplate, dir);
var packageName = workspace.name.substring('package:'.length);
analyzer = Analyzer.packages(
includedPaths: [p.normalize(dir.absolute.path)],
);
await for (var resolvedLibrary in analyzer.resolvedPublicLibraries()) {
var libraryPath = resolvedLibrary.element.source.fullName;
var dartLibraryPath = p.relative(libraryPath, from: libDirPath);
var htmlOutputPath = '${p.withoutExtension(dartLibraryPath)}.html';
var libraryContainer = workspace.addChild(
WorkspaceDirectory(workspace, dartLibraryPath),
);
var library = workspace.api.addLibrary(
resolvedLibrary.element,
workspace.name,
dartLibraryPath,
);
libraryContainer.mainFile = WorkspaceFile(
workspace,
dartLibraryPath,
htmlOutputPath,
emptyContentGenerator,
)..importScript = 'package:$packageName/$dartLibraryPath';
workspace.api.addResolution(library, libraryContainer.mainFile!);
for (var itemContainer in library.allChildrenSorted.whereType<Items>()) {
var path =
'${p.withoutExtension(dartLibraryPath)}/${itemContainer.name}.html';
var docFile = WorkspaceFile(
libraryContainer,
itemContainer.name,
path,
emptyContentGenerator,
);
libraryContainer.addChild(docFile);
workspace.api.addResolution(itemContainer, docFile);
}
}
workspace.api.finish();
}
void dispose() {
dir.deleteSync(recursive: true);
}
}
class NullLogger implements Logger {
final Ansi _ansi = Ansi(false);
@override
Ansi get ansi => _ansi;
@override
void flush() {}
@override
bool get isVerbose => false;
@override
Progress progress(String message) => NullProgress(message);
@override
void stderr(String message) {}
@override
void stdout(String message) {}
@override
void trace(String message) {}
@override
void write(String message) {}
@override
void writeCharCode(int charCode) {}
}
class NullProgress implements Progress {
@override
final String message;
final Stopwatch _timer = Stopwatch()..start();
NullProgress(this.message);
@override
void cancel() {}
@override
Duration get elapsed => _timer.elapsed;
@override
void finish({String? message, bool showTiming = false}) {
_timer.stop();
}
}