// Copyright (c) 2015, 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 'package:analyzer/analysis_rule/pubspec.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:analyzer/source/file_source.dart'; import 'package:analyzer/source/source.dart'; import 'package:source_span/source_span.dart'; import 'package:yaml/yaml.dart'; PubspecEntry? _findEntry( YamlMap map, String key, ResourceProvider? resourceProvider, ) { PubspecEntry? entry; map.nodes.forEach((k, v) { if (k is YamlScalar && key == k.toString()) { entry = _processScalar(k, v, resourceProvider); } }); return entry; } PubspecDependencyList? _processDependencies( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is! YamlMap) { return null; } _PubspecDependencyList deps = _PubspecDependencyList( PubspecNodeImpl(key, resourceProvider), ); value.nodes.forEach((k, v) { if (k is YamlScalar) deps.add(_PubspecDependency(k, v, resourceProvider)); }); return deps; } PubspecEnvironment? _processEnvironment( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is! YamlMap) { return null; } return _PubspecEnvironment( PubspecNodeImpl(key, resourceProvider), flutter: _findEntry(value, 'flutter', resourceProvider), sdk: _findEntry(value, 'sdk', resourceProvider), ); } PubspecGitRepo? _processGitRepo( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is YamlScalar) { var token = PubspecNodeImpl(key, resourceProvider); return _PubspecGitRepo( token, url: PubspecEntry(token, PubspecNodeImpl(value, resourceProvider)), ); } if (value is! YamlMap) { return null; } // url: git://github.com/munificent/kittens.git // ref: some-branch return _PubspecGitRepo( PubspecNodeImpl(key, resourceProvider), ref: _findEntry(value, 'ref', resourceProvider), url: _findEntry(value, 'url', resourceProvider), ); } PubspecHost? _processHost( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is YamlScalar) { // dependencies: // mypkg: // hosted: https://some-pub-server.com // version: ^1.2.3 return _PubspecHost( PubspecNodeImpl(key, resourceProvider), isShortForm: true, url: _processScalar(key, value, resourceProvider), ); } if (value is YamlMap) { // name: transmogrify // url: http://your-package-server.com return _PubspecHost( PubspecNodeImpl(key, resourceProvider), isShortForm: false, name: _findEntry(value, 'name', resourceProvider), url: _findEntry(value, 'url', resourceProvider), ); } return null; } PubspecEntry? _processScalar( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is! YamlScalar) { return null; //WARN? } return PubspecEntry( PubspecNodeImpl(key, resourceProvider), PubspecNodeImpl(value, resourceProvider), ); } PubspecNodeList? _processScalarList( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { if (value is! YamlList) { return null; } return _PubspecNodeList( PubspecNodeImpl(key, resourceProvider), value.nodes.whereType().map( (n) => PubspecNodeImpl(n, resourceProvider), ), ); } abstract class Pubspec { factory Pubspec.parse( String pubspec, { Uri? sourceUrl, ResourceProvider? resourceProvider, }) { try { var yaml = loadYamlNode(pubspec, sourceUrl: sourceUrl); return Pubspec.parseYaml(yaml, resourceProvider: resourceProvider); } on Exception { return _Pubspec.parse(YamlMap(), resourceProvider: resourceProvider); } } factory Pubspec.parseYaml( YamlNode yaml, { ResourceProvider? resourceProvider, }) { return _Pubspec.parse(yaml, resourceProvider: resourceProvider); } PubspecEntry? get author; PubspecNodeList? get authors; PubspecDependencyList? get dependencies; PubspecDependencyList? get dependencyOverrides; PubspecEntry? get description; PubspecDependencyList? get devDependencies; PubspecEntry? get documentation; PubspecEnvironment? get environment; PubspecEntry? get homepage; PubspecEntry? get issueTracker; PubspecEntry? get name; PubspecEntry? get repository; PubspecEntry? get resolution; PubspecEntry? get version; PubspecNodeList? get workspace; void accept(PubspecVisitor visitor); } class PubspecNodeImpl implements PubspecNode { @override final String? text; @override final SourceSpan span; final ResourceProvider _resourceProvider; PubspecNodeImpl(YamlScalar node, ResourceProvider? resourceProvider) : text = node.value?.toString(), span = node.span, _resourceProvider = resourceProvider ?? PhysicalResourceProvider.INSTANCE; /// The [Source] information of the pubspec file in which this node is located. Source get source { var uri = span.sourceUrl!; var filePath = _resourceProvider.pathContext.fromUri(uri); var file = _resourceProvider.getFile(filePath); return FileSource(file, uri); } @override String toString() => '$text'; } class _Pubspec implements Pubspec { @override final PubspecEntry? author; @override final PubspecNodeList? authors; @override final PubspecNodeList? workspace; @override final PubspecEntry? description; @override final PubspecEntry? documentation; @override final PubspecEnvironment? environment; @override final PubspecEntry? homepage; @override final PubspecEntry? issueTracker; @override final PubspecEntry? name; @override final PubspecEntry? repository; @override final PubspecEntry? resolution; @override final PubspecEntry? version; @override final PubspecDependencyList? dependencies; @override final PubspecDependencyList? devDependencies; @override final PubspecDependencyList? dependencyOverrides; factory _Pubspec.parse(YamlNode yaml, {ResourceProvider? resourceProvider}) { if (yaml is! YamlMap) { return _Pubspec._(); } PubspecEntry? author; PubspecNodeList? authors; PubspecNodeList? workspace; PubspecEntry? description; PubspecEntry? documentation; PubspecEnvironment? environment; PubspecEntry? homepage; PubspecEntry? issueTracker; PubspecEntry? name; PubspecEntry? repository; PubspecEntry? resolution; PubspecEntry? version; PubspecDependencyList? dependencies; PubspecDependencyList? devDependencies; PubspecDependencyList? dependencyOverrides; yaml.nodes.forEach((k, v) { if (k is! YamlScalar) { return; } YamlScalar key = k; switch (key.toString()) { case 'author': author = _processScalar(key, v, resourceProvider); case 'authors': authors = _processScalarList(key, v, resourceProvider); case 'homepage': homepage = _processScalar(key, v, resourceProvider); case 'repository': repository = _processScalar(key, v, resourceProvider); case 'issue_tracker': issueTracker = _processScalar(key, v, resourceProvider); case 'name': name = _processScalar(key, v, resourceProvider); case 'description': description = _processScalar(key, v, resourceProvider); case 'documentation': documentation = _processScalar(key, v, resourceProvider); case 'dependencies': dependencies = _processDependencies(key, v, resourceProvider); case 'dev_dependencies': devDependencies = _processDependencies(key, v, resourceProvider); case 'dependency_overrides': dependencyOverrides = _processDependencies(key, v, resourceProvider); case 'environment': environment = _processEnvironment(key, v, resourceProvider); case 'version': version = _processScalar(key, v, resourceProvider); case 'resolution': resolution = _processScalar(key, v, resourceProvider); case 'workspace': workspace = _processScalarList(key, v, resourceProvider); } }); return _Pubspec._( author: author, authors: authors, description: description, documentation: documentation, environment: environment, homepage: homepage, issueTracker: issueTracker, name: name, repository: repository, version: version, dependencies: dependencies, devDependencies: devDependencies, dependencyOverrides: dependencyOverrides, resolution: resolution, workspace: workspace, ); } _Pubspec._({ this.author, this.authors, this.workspace, this.description, this.documentation, this.environment, this.homepage, this.issueTracker, this.name, this.repository, this.version, this.dependencies, this.devDependencies, this.dependencyOverrides, this.resolution, }); @override void accept(PubspecVisitor visitor) { if (author case var author?) { visitor.visitPackageAuthor(author); } if (authors case var authors?) { visitor.visitPackageAuthors(authors); } if (description case var description?) { visitor.visitPackageDescription(description); } if (documentation case var documentation?) { visitor.visitPackageDocumentation(documentation); } if (environment case var environment?) { visitor.visitPackageEnvironment(environment); } if (homepage case var homepage?) { visitor.visitPackageHomepage(homepage); } if (issueTracker case var issueTracker?) { visitor.visitPackageIssueTracker(issueTracker); } if (repository case var repository?) { visitor.visitPackageRepository(repository); } if (name case var name?) { visitor.visitPackageName(name); } if (version case var version?) { visitor.visitPackageVersion(version); } if (dependencies case var dependencies?) { visitor.visitPackageDependencies(dependencies); dependencies.forEach(visitor.visitPackageDependency); } if (devDependencies case var devDependencies?) { visitor.visitPackageDevDependencies(devDependencies); devDependencies.forEach(visitor.visitPackageDevDependency); } if (dependencyOverrides case var dependencyOverrides?) { visitor.visitPackageDependencyOverrides(dependencyOverrides); dependencyOverrides.forEach(visitor.visitPackageDependencyOverride); } } @override String toString() { var sb = StringBuffer(); sb.maybeWrite(name); sb.maybeWrite(version); sb.maybeWrite(author); sb.maybeWrite(authors); sb.maybeWrite(description); sb.maybeWrite(homepage); sb.maybeWrite(repository); sb.maybeWrite(issueTracker); sb.maybeWrite(dependencies); sb.maybeWrite(devDependencies); sb.maybeWrite(dependencyOverrides); return sb.toString(); } } class _PubspecDependency extends PubspecDependency { @override final PubspecNode? name; @override final PubspecEntry? path; @override final PubspecEntry? version; @override final PubspecHost? host; @override final PubspecGitRepo? git; factory _PubspecDependency( YamlScalar key, YamlNode value, ResourceProvider? resourceProvider, ) { var name = PubspecNodeImpl(key, resourceProvider); PubspecEntry? path; PubspecEntry? version; PubspecHost? host; PubspecGitRepo? git; if (value is YamlScalar) { // Simple version constraint. version = PubspecEntry(null, PubspecNodeImpl(value, resourceProvider)); } else if (value is YamlMap) { value.nodes.forEach((k, v) { if (k is! YamlScalar) { return; } YamlScalar key = k; switch (key.toString()) { case 'path': path = _processScalar(key, v, resourceProvider); case 'version': version = _processScalar(key, v, resourceProvider); case 'hosted': host = _processHost(key, v, resourceProvider); case 'git': git = _processGitRepo(key, v, resourceProvider); } }); } return _PubspecDependency._( name: name, path: path, version: version, host: host, git: git, ); } _PubspecDependency._({ required this.name, required this.path, required this.version, required this.host, required this.git, }); @override String toString() { var sb = StringBuffer(); if (name != null) { sb.write('$name:'); } var versionInfo = ''; if (version != null) { if (version!.key == null) { versionInfo = ' $version'; } else { versionInfo = '\n $version'; } } sb.writeln(versionInfo); if (host != null) { sb.writeln(host); } if (git != null) { sb.writeln(git); } return sb.toString(); } } class _PubspecDependencyList extends PubspecDependencyList { final dependencies = []; final PubspecNode token; _PubspecDependencyList(this.token); @override Iterator get iterator => dependencies.iterator; void add(PubspecDependency? dependency) { if (dependency != null) { dependencies.add(dependency); } } @override String toString() => '$token\n${dependencies.join(' ')}'; } class _PubspecEnvironment implements PubspecEnvironment { @override final PubspecNode token; @override final PubspecEntry? flutter; @override final PubspecEntry? sdk; _PubspecEnvironment(this.token, {required this.flutter, required this.sdk}); @override String toString() => ''' $token: $sdk $flutter'''; } class _PubspecGitRepo implements PubspecGitRepo { @override final PubspecNode token; @override final PubspecEntry? ref; @override final PubspecEntry? url; _PubspecGitRepo(this.token, {this.ref, required this.url}); @override String toString() => ''' $token: $url $ref'''; } class _PubspecHost implements PubspecHost { @override final bool isShortForm; @override final PubspecEntry? name; @override final PubspecNode token; @override final PubspecEntry? url; _PubspecHost(this.token, {required this.isShortForm, this.name, this.url}); @override String toString() => ''' $token: $name $url'''; } class _PubspecNodeList extends PubspecNodeList { @override final PubspecNode token; final Iterable nodes; _PubspecNodeList(this.token, this.nodes); @override Iterator get iterator => nodes.iterator; @override String toString() => ''' $token: - ${nodes.join('\n - ')}'''; } extension on StringBuffer { void maybeWrite(Object? value) { if (value != null) { writeln(value); } } }