adding packages

This commit is contained in:
2026-01-15 14:38:46 -08:00
parent ef86e3ab6a
commit 6869cf47e5
5253 changed files with 726695 additions and 34 deletions
@@ -0,0 +1,52 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:io';
import 'package:ansicolor/ansicolor.dart';
import 'package:logging/logging.dart';
class AppLogger {
static AppLogger log = new AppLogger._('FlutterCli');
final Logger _logger;
AppLogger._(String name) : _logger = new Logger(name) {
final pens = {
Level.FINE: new AnsiPen()..blue(),
Level.INFO: new AnsiPen()..green(),
Level.WARNING: new AnsiPen()..magenta(),
Level.SEVERE: new AnsiPen()..red(bold: true)
};
Logger.root.onRecord.listen((record) {
for (final line in record.message.split('\n')) {
stderr
.writeln('${pens[record.level]('${record.level.name}:')} ${line}');
}
});
}
/// Configures the log level.
///
/// By default, FINE log will not be printed.
/// Sets [isVerbose] to print FINE logs also.
set isVerbose(bool value) {
Logger.root.level = value ? Level.FINE : Level.INFO;
}
void fine(message, [Object error, StackTrace stackTrace]) =>
_logger.log(Level.FINE, message, error, stackTrace);
void info(message, [Object error, StackTrace stackTrace]) =>
_logger.log(Level.INFO, message, error, stackTrace);
void warning(message, [Object error, StackTrace stackTrace]) =>
_logger.log(Level.WARNING, message, error, stackTrace);
void severe(message, [Object error, StackTrace stackTrace]) =>
_logger.log(Level.SEVERE, message, error, stackTrace);
}
@@ -0,0 +1,35 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:args/command_runner.dart';
import 'app_logger.dart';
import 'commands/generate.dart';
import 'commands/new.dart';
class NgDartCommanderRunner extends CommandRunner {
static const _verboseOption = 'verbose';
NgDartCommanderRunner()
: super('ngflutter', 'Ngflutter is a command line interface for Flutter.') {
argParser.addFlag(_verboseOption,
abbr: 'v',
help: 'Output extra logging information.',
defaultsTo: false);
addCommand(new NewProjectCommand());
addCommand(new GenerateCommand());
}
Future run(Iterable<String> args) async {
var option = super.parse(args);
AppLogger.log.isVerbose = option[_verboseOption];
await runCommand(option);
}
}
@@ -0,0 +1,51 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:recase/recase.dart';
/// Base class for commands for ngflutter executable.
abstract class NgDartCommand extends Command {
static const binaryName = 'ngflutter';
ArgParser get argParser => _argParser;
final _argParser = new ArgParser(allowTrailingOptions: true);
/// Reads argument for current command.
String readArg(String errorMessage) {
var args = argResults.rest;
if (args == null || args.length == 0) {
// Usage is provided by command runner.
throw new UsageException(errorMessage, '');
}
var arg = args.first;
args = args.skip(1).toList();
if (args.length > 0) {
throw new UsageException('Unexpected argument $args', '');
}
return arg;
}
/// Reads argument for current command and create an EntityName.
ReCase readArgAsEntityName(String errorMessage) =>
getEntityName(readArg(errorMessage));
ReCase getEntityName(String entity) {
ReCase entityName;
try {
entityName = new ReCase(entity);
} on ArgumentError catch (error) {
throw new UsageException(error.message, '');
}
return entityName;
}
}
@@ -0,0 +1,22 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'command.dart';
import 'generate_test.dart';
import 'package:ngflutter/src/commands/generate_widget.dart';
/// Handles the `generate` ngflutter command.
class GenerateCommand extends NgDartCommand {
String get name => 'generate';
String get description => 'Generate component or test.';
String get invocation => '${NgDartCommand.binaryName} generate <subcommand>';
GenerateCommand() {
addSubcommand(new GenerateTestCommand());
addSubcommand(new GenerateWidgetCommand());
}
}
@@ -0,0 +1,50 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import '../generators/test.dart';
import '../path_util.dart';
import 'command.dart';
/// Handles the `generate test` ngflutter command.
class GenerateTestCommand extends NgDartCommand {
static const _classOption = 'class';
static const _testPathOption = 'path';
static const _tagOption = 'tag';
String get name => 'test';
String get description => 'Generate Flutter component test, '
'this command should be run under root directory of the project.';
String get invocation => '${NgDartCommand.binaryName} generate test '
'<component/file/path> [--class <class name>] [--path <test/file/path>] '
'[--tag <test tag>]';
String get _classUnderTest => argResults[_classOption];
String get _testPath => getNormalizedPath(argResults[_testPathOption]);
String get _testTag => argResults[_tagOption];
GenerateTestCommand() {
argParser.addOption(_classOption,
abbr: 'c',
help: 'Flutter component class to be tested. '
'Will select one from the specified file if it is null.',
defaultsTo: null);
argParser.addOption(_testPathOption,
abbr: 'p', help: 'Test file path', defaultsTo: 'test');
argParser.addOption(_tagOption,
help: 'Tag for the test', defaultsTo: 'aot');
}
Future run() async {
await new TestGenerator(
_testTag,
readArg('path for Flutter component file is needed.'),
_classUnderTest,
_testPath)
.generate();
}
}
@@ -0,0 +1,34 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import '../generators/widget.dart';
import '../path_util.dart';
import 'command.dart';
/// Handles the `generate widget` ngflutter command.
class GenerateWidgetCommand extends NgDartCommand {
static const _pathOption = 'path';
String get name => 'widget';
String get description => 'Generate Flutter Widget.';
String get invocation => '${NgDartCommand.binaryName} generate widget '
'<WidgetName> [--path <widget/file/path>]';
String get _widgetPath => getNormalizedPath(argResults[_pathOption]);
GenerateWidgetCommand() {
argParser.addOption(_pathOption,
abbr: 'p', help: 'Widget file path', defaultsTo: 'lib/ui/common');
}
Future run() async {
await new WidgetGenerator(
readArgAsEntityName('Widget name is needed.'), _widgetPath)
.generate();
}
}
@@ -0,0 +1,43 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import '../generators/project.dart';
import '../path_util.dart';
import 'command.dart';
/// Handles the `new` ngflutter command.
class NewProjectCommand extends NgDartCommand {
static const _rootComponentOption = 'root_component';
static const _pathOption = 'path';
String get name => 'new';
String get description => 'Create an Flutter project.';
String get invocation => '${NgDartCommand.binaryName} new <project_name> '
'[--path <project/path>] [--root_component <RootComponentName>]';
String get _rootComponent => argResults[_rootComponentOption];
String get _projectPath => getNormalizedPath(argResults[_pathOption]);
NewProjectCommand() {
argParser.addOption(_pathOption,
abbr: 'p',
help: 'Project path, '
'a new folder will be created unde this path for the project.',
defaultsTo: '.');
argParser.addOption(_rootComponentOption,
abbr: 'r',
help: 'Class name of root component.',
defaultsTo: 'Screen');
}
Future run() async {
await new ProjectGenerator(readArgAsEntityName('Project name is needed.'),
_projectPath, getEntityName(_rootComponent))
.generate();
}
}
@@ -0,0 +1,68 @@
// // Copyright 2017 Google Inc.
// //
// // Use of this source code is governed by a BSD-style
// // license that can be found in the LICENSE file or at
// // https://developers.google.com/open-source/licenses/bsd
// /// Entity name which can produce multiple formats.
// class EntityName {
// static final validPatterns = <RegExp>[
// // abc_bcd
// new RegExp(r'^[a-z][a-z0-9]*(_[a-z0-9]+)*$'),
// // AbcBcd or abcBcd
// new RegExp(r'^([A-Za-z][a-z0-9]*)*$'),
// // abc-bcd
// new RegExp(r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$')
// ];
// static final splitPattern = new RegExp(r'(?=[A-Z])|_|-');
// // Segments for the name.
// //
// // Each segment is a word in the name in its lowercase format.
// final List<String> _segments;
// /// Camel Cased format.
// ///
// /// Example: AbcBcdCde.
// String get camelCased =>
// _segments.map((s) => '${s[0].toUpperCase()}${s.substring(1)}').join('');
// /// Lower Camel Cased format.
// ///
// /// Example: abcBcdCde.
// String get lowerCamelCased =>
// '${camelCased[0].toLowerCase()}${camelCased.substring(1)}';
// /// Underscored format.
// ///
// /// Example: abc_bcd_cde.
// String get underscored => _segments.join('_');
// /// Dashed format.
// ///
// /// Example: abc-bcd-cde.
// String get dashed => _segments.join('-');
// /// Space separated format.
// ///
// /// Example: Abc Bcd Cde.
// String get spaced =>
// _segments.map((s) => '${s[0].toUpperCase()}${s.substring(1)}').join(' ');
// /// Accepts multiple formats of the name.
// ///
// /// Currently patterns like abc_bcd, AbcBcd, abcBcd, abc-bcd are supported.
// factory EntityName(String name) {
// if (!validPatterns.any((pattern) => pattern.hasMatch(name))) {
// throw new ArgumentError(
// '$name is not valid. It should be of form "abc_bcd", '
// '"AbcBcd", "abcBcd", or "abc-bcd".');
// }
// final segments =
// name.split(splitPattern).map((s) => s.toLowerCase()).toList();
// return new EntityName._(segments);
// }
// EntityName._(this._segments);
// }
@@ -0,0 +1,16 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
export 'package:args/command_runner.dart' show UsageException;
/// An exception class for invalid expression when analyzing the dart file.
class InvalidExpressionError extends Error {
final String message;
InvalidExpressionError(this.message);
String toString() => "Invalid expression: $message";
}
@@ -0,0 +1,20 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:convert';
import 'dart:io';
class FileReader {
static FileReader reader = new FileReader._();
FileReader._();
String readAsString(String filePath, {Encoding encoding: utf8}) =>
new File(filePath).readAsStringSync(encoding: encoding);
List<String> readAsLines(String filePath, {Encoding encoding: utf8}) =>
new File(filePath).readAsLinesSync(encoding: encoding);
}
@@ -0,0 +1,32 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:io';
import 'app_logger.dart';
class FileWriter {
static FileWriter writer = new FileWriter._();
FileWriter._();
/// Writes content to [destination]. Throws StateError if
/// destination exists. Folder will be created if not exists.
void write(String destination, String content) {
var file = new File(destination);
if (file.existsSync()) {
throw new StateError('File $destination already exists');
}
if (!file.parent.existsSync()) {
file.parent.createSync(recursive: true);
}
AppLogger.log.info('Saving $destination');
file.writeAsStringSync(content);
}
}
@@ -0,0 +1,33 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:path/path.dart' as path;
import 'file_writer.dart';
import 'template_file.dart';
/// An abstract class defines a template generator.
abstract class Generator {
final String _destinationFolder;
Generator(this._destinationFolder);
/// Renders templates and writes to target files.
Future renderAndWriteTemplates(Map<String, String> templateTargets) async {
for (final template in templateTargets.keys) {
final content = await new TemplateFile(template, this).renderString();
FileWriter.writer.write(
path.join(_destinationFolder, templateTargets[template]), content);
}
}
/// Generates files defined for this generator.
Future generate();
Map<String, String> toMap();
}
@@ -0,0 +1,76 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:path/path.dart' as path;
import '../file_reader.dart';
import '../generator.dart';
import '../page_object_data.dart';
import 'package:ngflutter/src/visitors/component_info.dart';
/// Generator for page object.
class PoGenerator extends Generator {
static const _templateFolder = 'pageobject';
static const _templateFileName = 'po.dart.mustache';
final String componentPath;
final String poClassName;
final String poFileName;
final PageObjectData pageObjectData;
PoGenerator._(this.componentPath, this.poClassName, this.poFileName,
this.pageObjectData, String destinationFolder)
: super(destinationFolder);
factory PoGenerator(ComponentInfo componentInfo, String componentPath,
String poClassName, String destinationFolder) {
PageObjectData pageObjectData;
if (componentInfo.inlineTemplate == null) {
// componentInfo.templatePath is not null here
var componentTemplateFilePath =
path.join(path.dirname(componentPath), componentInfo.templatePath);
pageObjectData = new PageObjectData(
FileReader.reader.readAsString(componentTemplateFilePath));
} else {
pageObjectData = new PageObjectData(componentInfo.inlineTemplate);
}
return new PoGenerator._(
componentPath,
poClassName,
'${path.basenameWithoutExtension(componentPath)}_po.dart',
pageObjectData,
destinationFolder);
}
// Gets a map from template file name to target file name.
Map<String, String> _getTemplateTargetPaths() {
var results = <String, String>{};
results[path.join(_templateFolder, _templateFileName)] = poFileName;
return results;
}
@override
Future generate() async {
await renderAndWriteTemplates(_getTemplateTargetPaths());
}
@override
Map<String, String> toMap() {
return {
"componentPath": componentPath,
"poClassName": poClassName,
"poFileName": poFileName
};
}
}
@@ -0,0 +1,71 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:recase/recase.dart';
import '../generator.dart';
import 'widget.dart';
/// Generator for sample Flutter project.
class ProjectGenerator extends Generator {
static const _templateFolder = 'project';
static final List<String> _templateFilePaths = [
path.join('web', 'index.html.mustache'),
path.join('lib', 'main.dart.mustache'),
'analysis_options.yaml',
'.gitignore',
'pubspec.yaml.mustache',
];
/// Project name in format abc_bcd.
final String name;
final String description;
/// Root component of this project.
final WidgetGenerator component;
ProjectGenerator._(
this.name, this.description, this.component, String destinationFolder)
: super(destinationFolder);
factory ProjectGenerator(ReCase projectEntityName, String destinationFolder,
ReCase componentClassEntityName) {
destinationFolder =
path.join(destinationFolder, projectEntityName.snakeCase);
var component = new WidgetGenerator(
componentClassEntityName, path.join(destinationFolder, 'lib/ui/home'));
return new ProjectGenerator._(projectEntityName.snakeCase,
projectEntityName.titleCase, component, destinationFolder);
}
// Gets a map from template file name to target file name.
Map<String, String> _getTemplateTargetPaths() {
var results = <String, String>{};
for (final templatePath in _templateFilePaths) {
results[path.join(_templateFolder, templatePath)] =
templatePath.replaceAll('.mustache', '');
}
return results;
}
@override
Future generate() async {
await renderAndWriteTemplates(_getTemplateTargetPaths());
await component.generate();
}
@override
Map<String, String> toMap() {
return {
"name": name,
"description": description,
};
}
}
@@ -0,0 +1,71 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:ngflutter/src/project_model.dart';
import 'package:path/path.dart' as path;
import '../generator.dart';
import 'po.dart';
/// Generator for component test.
class TestGenerator extends Generator {
static const _templateFolder = 'test';
static const _templateFileName = 'test.dart.mustache';
static final _suffixPattern = new RegExp(r'(Component|View|PO|UnitTestPO)*$');
final String tag;
final String componentPath;
final ProjectModel projectModel;
final PoGenerator poGenerator;
TestGenerator._(this.tag, this.componentPath, this.projectModel,
this.poGenerator, String destinationFolder)
: super(destinationFolder);
factory TestGenerator(String tag, String componentPath, String className,
String destinationFolder) {
var projectModel =
new ProjectModel('.packages', 'pubspec.yaml', componentPath, className);
var poClassName =
projectModel.componentClassName.replaceAll(_suffixPattern, '') + 'PO';
var poGenerator = new PoGenerator(
projectModel.components[projectModel.componentClassName],
componentPath,
poClassName,
destinationFolder);
return new TestGenerator._(
tag, componentPath, projectModel, poGenerator, destinationFolder);
}
// Gets a map from template file name to target file name.
Map<String, String> _getTemplateTargetPaths() {
var results = <String, String>{};
results[path.join(_templateFolder, _templateFileName)] =
'${path.basenameWithoutExtension(componentPath)}_test.dart';
return results;
}
@override
Future generate() async {
await renderAndWriteTemplates(_getTemplateTargetPaths());
await poGenerator.generate();
}
@override
Map<String, String> toMap() {
return {
"tag": tag,
"componentPath": componentPath,
};
}
}
@@ -0,0 +1,70 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:recase/recase.dart';
import '../generator.dart';
/// Generator for Flutter Widget.
class WidgetGenerator extends Generator {
static const _templateFolder = 'widgets';
static const List<String> _templateFileNames = const [
'widget.dart.mustache',
];
/// Class name of this Widget.
final String className;
final String selector;
/// Widget file name without extension.
final String targetName;
WidgetGenerator._(
this.className, this.selector, this.targetName, String destinationFolder)
: super(destinationFolder);
factory WidgetGenerator(
ReCase classEntityName,
String destinationFolder,
) {
return new WidgetGenerator._(
classEntityName.pascalCase,
classEntityName.paramCase,
classEntityName.snakeCase,
destinationFolder);
}
// Gets a map from template file name to target file name.
Map<String, String> _getTemplateTargetPaths() {
var results = <String, String>{};
for (String templateFileName in _templateFileNames) {
final _template = path.join(_templateFolder, templateFileName);
final _path = '$targetName.${templateFileName.split('.')[1]}';
print('Path: $_template -> $_path');
results[_template] = _path;
}
return results;
}
@override
Future generate() async {
await renderAndWriteTemplates(_getTemplateTargetPaths());
}
@override
Map<String, String> toMap() {
return {
"className": className,
"selector": selector,
"targetName": targetName,
};
}
}
@@ -0,0 +1,59 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'exceptions.dart';
import 'file_reader.dart';
import 'path_util.dart';
/// Class to convert a package URI to file URI.
class PackageUriResolver {
/// Path of file .packages
final String _dotPackagesFilePath;
/// Maps package name to folder URI of this package.
Map<String, String> _packageMap;
PackageUriResolver(this._dotPackagesFilePath);
void _buildPackageMap() {
List<String> lines;
try {
lines = FileReader.reader.readAsLines(_dotPackagesFilePath);
} catch (e) {
throw new UsageException(
'Error when reading $_dotPackagesFilePath, '
'please run pub get first.',
'');
}
_packageMap = <String, String>{};
for (var line in lines) {
if (line.startsWith('#')) continue;
var commaPosition = line.indexOf(':');
if (commaPosition == -1) continue;
_packageMap[line.substring(0, commaPosition)] =
line.substring(commaPosition + 1);
}
}
/// Resolves a package URI to a file path.
String resolve(String packageUri) {
if (_packageMap == null) _buildPackageMap();
var packageName = getPackageName(packageUri);
if (_packageMap[packageName] == null) {
throw new UsageException(
'Cannot locate $packageName, '
'probably you need to run pub get again',
'');
}
var packagePath = getPath(packageUri);
return Uri.parse('${_packageMap[packageName]}$packagePath').toFilePath();
}
}
@@ -0,0 +1,167 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:html/dom.dart';
import 'package:html/parser.dart' show parseFragment;
import 'visitors/dart_class_info.dart';
/// Data for generating page object.
class PageObjectData {
static const pageLoaderDependency = 'package:pageloader/objects.dart';
static const asyncDependency = 'dart:async';
/// Common dependencies for page object classes.
List<String> get commonDependencies => [pageLoaderDependency]
..addAll(variables.isNotEmpty ? [asyncDependency] : []);
final List<_Variable> variables;
final DocumentFragment document;
factory PageObjectData(String templateFileContent) {
var document = parseFragment(templateFileContent);
var variables = document
.querySelectorAll('*')
.map((element) => new _Variable.fromElement(element))
.where((variable) => variable != null)
.toList(growable: false)
..sort();
return new PageObjectData._internal(document, variables);
}
PageObjectData._internal(this.document, this.variables);
}
class _Selector {
static const ignoredTags = const [
'a',
'p',
'b',
'i',
'small',
'strong',
'object',
'canvas',
'table',
'span',
'img',
'form',
'fieldset',
'li',
'center',
'label',
'br',
'legend',
'ul',
'ng-content'
];
final String type;
final String name;
final String value;
factory _Selector.fromElement(Element element) {
if (ignoredTags.contains(element.localName)) {
return null;
}
if (element.id.isNotEmpty) {
return new _Selector('ById', element.id);
}
if (element.classes.isNotEmpty) {
return new _Selector('ByClass', element.classes.first);
}
return new _Selector('ByTagName', element.localName);
}
_Selector(this.type, this.name, [this.value]);
@override
String toString() => "@$type('${value ?? name}')";
}
class _Variable implements Comparable<_Variable> {
static final wordReg = new RegExp(r'(^|[\-._])(\w)');
final Element element;
final _Selector selector;
final bool isList;
final bool isOptional;
final DartClassInfo type;
final String name;
factory _Variable.fromElement(Element element) {
var selector = new _Selector.fromElement(element);
if (selector == null) {
return null;
}
var type = new DartClassInfo(
'PageLoaderElement', 'package:pageloader/objects.dart');
return new _Variable(element, _getCamelCasedName(selector.name), selector,
type, _isElementInList(element), _isElementInIf(element));
}
_Variable(this.element, this.name, this.selector, this.type,
[this.isList = false, this.isOptional = false]);
static bool _isElementInList(Element element) {
while (element != null) {
if (element.attributes.keys.any((a) => ['*ngfor'].contains(a))) {
return true;
}
element = element.parent;
}
return false;
}
static bool _isElementInIf(Element element) {
while (element != null) {
if (element.attributes.keys.contains('*ngif') ||
element.localName == 'template' &&
element.attributes.keys.contains('[ngif]')) {
return true;
}
element = element.parent;
}
return false;
}
static String _getCamelCasedName(String name) =>
name.replaceAllMapped(wordReg, (m) => m[2].toUpperCase());
String get internalString => '${_getOptionalString()}'
'$selector\n'
' Lazy<${_getTypeString()}> _get$name;';
String get getterString =>
'Future<${_getTypeString()}> get $_getFirstCharacterLoweredName'
' => _get$name();';
String _getOptionalString() => isOptional ? '@optional\n' : '';
String _getTypeString() {
var s = type.className;
if (isList) {
s = 'List<$s>';
}
return s;
}
String get _getFirstCharacterLoweredName =>
name.replaceFirstMapped(new RegExp('^(.)'), (m) => m[1].toLowerCase());
@override
int compareTo(_Variable other) => name.compareTo(other.name);
}
@@ -0,0 +1,24 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:path/path.dart' as path;
/// Fixes invalid path.
///
/// For example:
/// 'path\to/folder' -> 'path/to/folder' (posix)
String getNormalizedPath(String oldPath) =>
path.normalize(path.joinAll(oldPath.split(new RegExp(r'[\\/]')))).trim();
/// Converts '\' in [uri] into '/'.
String fixUri(String uri) => uri.replaceAll('\\', '/');
/// Extracts package name from package [uri].
String getPackageName(String uri) =>
uri.substring(0, uri.indexOf('/')).replaceAll('package:', '');
/// Extracts path from package [uri].
String getPath(String uri) => uri.substring(uri.indexOf('/') + 1);
@@ -0,0 +1,179 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:path/path.dart' as path;
import 'exceptions.dart';
import 'file_reader.dart';
import 'package_uri_resolver.dart';
import 'path_util.dart';
import 'visitors/flutter_component_visitor.dart';
import 'visitors/ast_cache.dart';
import 'visitors/binding_info.dart';
import 'visitors/binding_visitor.dart';
import 'visitors/component_info.dart';
import 'visitors/dart_class_info.dart';
import 'visitors/dart_class_visitor.dart';
import 'visitors/visit_resources.dart';
/// Project model built from AST cache.
///
/// The project model built will contains all referenced Dart classes,
/// all Flutter component classes, and all binding modules.
class ProjectModel {
final String projectName;
/// Name of component class to be tested.
final String componentClassName;
/// Service classes needed when construct the component
/// class [componentClassName].
final List<String> serviceClasses;
/// Package URI of [componentClassName].
final String componentClassUri;
/// Map from type name to info for each Dart class.
final Map<String, DartClassInfo> dartClasses;
/// Map from type name to info of each Flutter component.
final Map<String, ComponentInfo> components;
/// Map from var name to info of bindings.
final Map<String, ModuleInfo> modules;
ProjectModel._(this.projectName, this.componentClassName, this.serviceClasses,
this.componentClassUri, this.dartClasses, this.components, this.modules);
/// Whether providers are needed when generating test.
bool get needProviders => serviceClasses != null && serviceClasses.isNotEmpty;
/// Uris for service classes used.
List<String> get referencedUris => serviceClasses
.map((className) => dartClasses[className].uri)
.toList(growable: false);
factory ProjectModel(String dotPackagesFilePath, String pubspecFilePath,
String componentPath, String className) {
var projectName = _getProjectName(pubspecFilePath);
var libPrefix = 'lib${path.separator}';
var componentClassUri = componentPath.startsWith(libPrefix)
? fixUri(
'package:$projectName/${componentPath.substring(libPrefix.length)}')
: fixUri('package:$projectName/$componentPath');
var uriResolver = new PackageUriResolver(dotPackagesFilePath);
var asts = new AstCache(componentClassUri, uriResolver);
asts.build();
var dartClasses = <String, DartClassInfo>{};
dartClasses.addAll(visitUris<DartClassInfo>(
asts, (file, out) => new DartClassVisitor(file, out, asts.publicUris)));
var components = <String, ComponentInfo>{};
components.addAll(visitUris<ComponentInfo>(
asts, (_, out) => new FlutterComponentVisitor(dartClasses, out)));
var modules = <String, ModuleInfo>{};
modules.addAll(visitUris<ModuleInfo>(
asts,
(file, out) => new BindingVisitor(
file, out, asts.publicUris, _getBindingVariables(components))));
var componentClassName = className;
if (componentClassName == null) {
componentClassName =
_getComponentClassName(componentClassUri, components);
}
var serviceClasses = _getServiceClasses(
componentClassName, dartClasses, components, modules);
return new ProjectModel._(projectName, componentClassName, serviceClasses,
componentClassUri, dartClasses, components, modules);
}
}
/// Gets project name from pubsepc [pubspecFilePath].
String _getProjectName(String pubspecFilePath) {
List<String> lines;
try {
lines = FileReader.reader.readAsLines(pubspecFilePath);
} catch (e) {
throw new UsageException(
'Error happened when reading pubspec.yaml. '
'Command generate test should be run '
'under root directory of the project.',
'');
}
var namePrefix = 'name:';
for (var line in lines) {
line = line.trim();
if (line.startsWith(namePrefix)) {
return line.substring(namePrefix.length).trim();
}
}
throw new FormatException('Invalid pubspec.yaml: cannot find project name');
}
/// Gets binding variables used in providers of @Component.
Set<String> _getBindingVariables(Map<String, ComponentInfo> components) {
var result = new Set<String>();
for (var component in components.values) {
if (component.module == null) continue;
for (var binding in component.module.directChildren) {
if (binding is String) result.add(binding);
}
}
return result;
}
/// Gets one component class name from [componentClassUri].
String _getComponentClassName(
String componentClassUri, Map<String, ComponentInfo> components) {
for (var component in components.values) {
if (component.classInfo.uri == componentClassUri) {
return component.classInfo.className;
}
}
throw new UsageException(
'Cannot find a component class in specified path.', '');
}
/// Gets all service classes used in [componentClassName] that need binding.
///
/// Should return an empty list if no service class is used.
List<String> _getServiceClasses(
String componentClassName,
Map<String, DartClassInfo> dartClasses,
Map<String, ComponentInfo> components,
Map<String, ModuleInfo> modules) {
var dependencies = <String>[];
for (var parameter in dartClasses[componentClassName].constructorParameters) {
var service = parameter.dependency;
if (dartClasses[service].uri == null) continue;
dependencies.add(service);
}
var module = components[componentClassName].module;
if (module != null) {
for (var binding in module.getAllBindingInstances(modules)) {
if (dependencies.contains(binding.className)) {
dependencies.remove(binding.className);
}
}
}
var serviceClasses = <String>[]..addAll(dependencies);
return serviceClasses;
}
@@ -0,0 +1,45 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:async';
import 'dart:io';
import "dart:convert" show utf8;
// import 'package:analyzer/file_system/file_system.dart';
// import 'package:flutter/services.dart';
import 'package:reflected_mustache/mustache.dart';
// import 'package:resource/resource.dart' show Resource;
import 'generator.dart';
import 'path_util.dart';
/// Template file class wrapping operations on mustache template.
class TemplateFile {
/// Template file path relative to templates/.
final String _path;
/// Data for this template.
final Generator _data;
TemplateFile(this._path, this._data);
/// Renders template file on [_path] with values from [_data].
Future<String> renderString() async {
var uri = fixUri('lib/templates/$_path');
// var uri = fixUri('package:ngflutter/templates/$_path');
// var content = await rootBundle.loadString(uri);
var resource = new File(uri);
var content = await resource.readAsString(encoding: utf8);
var template = new Template(content);
return template.renderString(_data.toMap());
}
}
// var template = new Template(source, name: 'template-filename.html');
// final String output = template.renderString(
// {'className': 'FlutterWidget'},
// );
@@ -0,0 +1,125 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'dart:collection';
import 'package:analyzer/analyzer.dart';
import 'package:path/path.dart' as path;
import '../app_logger.dart';
import '../exceptions.dart';
import '../file_reader.dart';
import '../package_uri_resolver.dart';
import '../path_util.dart';
/// Caches the output of the Dart analyzer to avoid re-analyzing dart resources.
class AstCache {
/// Package URI that needs to be parsed.
final String _uri;
final PackageUriResolver _uriResolver;
/// Maps URI to compilation unit.
final Map<String, CompilationUnit> _uriToAst = {};
/// Maps an internal URI to a public one.
final Map<String, String> publicUris = {};
/// Creates AST cache from [_uri] and files referenced.
///
/// Caller should make sure that [_uri] starts with 'package';
AstCache(this._uri, this._uriResolver);
/// Parses the source file and create AST cache.
void build() {
publicUris[_uri] = _uri;
_setAst(_uri);
// Collects imported URIs using BFS.
var queue = new Queue<String>();
queue.add(_uri);
while (queue.isNotEmpty) {
var head = queue.removeFirst();
var compilationUnit = _uriToAst[head];
for (var uri in _getReferencedUris(head, compilationUnit)) {
if (_uriToAst[uri] == null) {
_setAst(uri);
queue.add(uri);
}
}
}
}
/// Gets AST cache for [uri].
CompilationUnit getCompilationUnit(String uri) {
if (_uriToAst[uri] == null) _setAst(uri);
return _uriToAst[uri];
}
List<String> get allUris => _uriToAst.keys.toList(growable: false);
/// Parses [uri] into AST and set it to _uriToAst[uri].
void _setAst(String uri) {
if (_uriToAst[uri] != null) return;
if (_uriToAst[uri] != null) return;
CompilationUnit compilationUnit;
try {
var filePath = _uriResolver.resolve(uri);
AppLogger.log.fine('Parsing file $filePath...');
compilationUnit = parseCompilationUnit(
FileReader.reader.readAsString(filePath),
name: filePath);
} on UsageException {
rethrow;
} catch (e) {
AppLogger.log.warning('Could not parse $uri: $e');
compilationUnit = parseCompilationUnit('');
}
_uriToAst[uri] = compilationUnit;
}
/// Gets all files imported or exported by [uri].
///
/// This function will also set [publicUris] in case an implementation file
/// is exported.
Set<String> _getReferencedUris(String uri, CompilationUnit compilationUnit) {
var results = new Set<String>();
for (var directive in compilationUnit.directives) {
if (directive is! ImportDirective && directive is! ExportDirective) {
continue;
}
var referencedUri = (directive as UriBasedDirective).uri.stringValue;
// Skips dart imports.
if (referencedUri.startsWith('dart:')) continue;
if (referencedUri.startsWith('package:')) {
// Skips Flutter imports.
if (getPackageName(referencedUri) == 'flutter') continue;
} else {
// Relative path.
var directoryName = path.posix.dirname(getPath(uri));
var referencedFile =
path.posix.normalize(path.posix.join(directoryName, referencedUri));
var packageName = getPackageName(uri);
referencedUri =
'package:${path.posix.join(packageName, referencedFile)}';
}
results.add(referencedUri);
if (directive is ExportDirective && referencedUri.contains('/src/')) {
publicUris[referencedUri] = publicUris[uri];
} else {
publicUris[referencedUri] = referencedUri;
}
}
return results;
}
}
@@ -0,0 +1,184 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import '../app_logger.dart';
import '../exceptions.dart';
import 'binding_info.dart';
import 'utils.dart';
/// Extracts binding information from list literal.
///
/// Given a list of bindings in [bindingList], extracts all information to
/// [module].
void extractBindingInfo(ListLiteral bindingList, ModuleInfo module) {
for (var node in bindingList.elements) {
try {
processBindingElement(node, module);
} on UnsupportedError catch (e) {
AppLogger.log.fine(e.message);
}
}
}
/// Parses a single binding creation expression.
///
/// For one binding expression [node], saves the information to [module].
/// For code block below:
///
/// const testBindings = Const [
/// X,
/// otherBindings,
/// const Binding(A, toAlias: B)
/// ]
///
/// Simple identifier "X" and "otherBindings" will be stored in
/// directChildren of [module] as a String, and binding creation expression
/// "const Binding(A, toAlias: B)" will be parsed into a BindingInstance
/// and be stored in directChildren of [module].
void processBindingElement(Expression node, ModuleInfo module) {
Object binding;
if (node is SimpleIdentifier) {
// Ignores PrefixedIdentifier here. node here can be
// a module (binding list) or
// an OpaqueToken or
// a class name.
binding = extractName(node);
} else if (node is InstanceCreationExpression) {
binding = _getBindingFromCreationExpression(node);
} else if (node is MethodInvocation) {
binding = _getBindingFromMethodInvocation(node);
} else {
throw new UnsupportedError('Unable to handle $node.');
}
if (binding != null) module.directChildren.add(binding);
}
/// Extracts binding information from instance creation expression [node].
/// [node] can be something like below.
///
/// const/new Provider(Expression, ...)
BindingInstance _getBindingFromCreationExpression(
InstanceCreationExpression node) {
if (extractName(node.constructorName.type.name) != 'Provider') {
throw new UnsupportedError(
'Unable to handle ${node.constructorName.type.name} in $node');
}
return _buildBindingInstance(node.argumentList.arguments, node.toString());
}
/// Extracts binding information from method invocation [node].
/// [node] can be something like below.
///
/// provide(ClassABC, ...).
BindingInstance _getBindingFromMethodInvocation(MethodInvocation node) {
if (node.target != null) {
throw new UnsupportedError('Unable to handle ${node.target} in $node');
}
if (node.methodName.name != 'provide') {
throw new UnsupportedError(
'Unable to handle ${node.methodName.name} in $node');
}
return _buildBindingInstance(node.argumentList.arguments, node.toString());
}
BindingInstance _buildBindingInstance(
NodeList<Expression> args, String creationExpression) {
if (args.isEmpty) throw new InvalidExpressionError(creationExpression);
BindingInstance binding;
var token = args[0];
if (token is SimpleIdentifier || token is PrefixedIdentifier) {
// Expression is an OpaqueToken or a class name.
binding = new BindingInstance(extractName(token), creationExpression);
} else if (token is InstanceCreationExpression) {
// Expression is to create a class instance.
binding = new BindingInstance(
extractName(token.constructorName.type.name), creationExpression);
} else if (token is SimpleStringLiteral) {
binding = new BindingInstance(token.value, creationExpression);
}
if (binding != null) {
_handleBindingArgs(args, binding);
return binding;
}
throw new UnsupportedError('Unable to handle $token '
'(${token.runtimeType}) in $creationExpression');
}
/// Extracts name of the class from [expression] used in toClass / toAlias.
String _extractClassName(Expression expression) {
if (expression is SimpleIdentifier || expression is PrefixedIdentifier) {
return extractName(expression);
}
throw new UnsupportedError(
'Unable to handle $expression for toClass / toAlias');
}
/// Collects all referenced classes in binding creation [expression]
/// to [bindingInstance] recursively.
void _addReferencedClasses(
Expression expression, BindingInstance bindingInstance) {
if (expression is Identifier) {
bindingInstance.referencedClasses.add(extractName(expression));
} else if (expression is InstanceCreationExpression) {
// Annotation, skip.
AppLogger.log.fine('Ignore expression "$expression" in deps');
} else if (expression is ListLiteral) {
for (var dependency in expression.elements) {
_addReferencedClasses(dependency, bindingInstance);
}
} else {
throw new InvalidExpressionError(expression.toString());
}
}
/// Extracts binding information from arguments in calling
///
/// const/new Provider(token, ...)
/// provider(token, ...)
void _handleBindingArgs(
NodeList<Expression> args, BindingInstance bindingInstance) {
// Element 0 is already processed by caller.
for (var i = 1; i < args.length; ++i) {
if (args[i] is! NamedExpression) {
throw new InvalidExpressionError('Invalid parameter ${args[i]} at $i');
}
var namedExpression = args[i] as NamedExpression;
var expression = namedExpression.expression;
switch (namedExpression.name.label.name) {
case 'useClass': // Fallthrough, they share the same format.
case 'useExisting':
bindingInstance.referencedClasses.add(_extractClassName(expression));
break;
case 'useValue':
if (expression is InstanceCreationExpression) {
bindingInstance.referencedClasses
.add(extractName(expression.constructorName.type.name));
}
break;
case 'useFactory':
break;
case 'multi': // Do nothing.
break;
case 'deps':
_addReferencedClasses(expression, bindingInstance);
break;
default:
throw new UnsupportedError('Unimplemented named expression:'
' ${namedExpression.name.label.name}');
}
}
}
@@ -0,0 +1,81 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
/// Base class for binding information.
abstract class BindingInfo {
/// String representation of this binding.
String get expression;
}
/// Expression that creates a Provider.
class BindingInstance extends BindingInfo {
final String className;
final String creationExpression;
/// Classes used in [creationExpression].
final Set<String> referencedClasses = new Set();
BindingInstance(this.className, this.creationExpression);
@override
String toString() => creationExpression;
@override
String get expression => creationExpression;
}
/// Models binding information.
class ModuleInfo extends BindingInfo {
/// Uri of file that contains this binding module.
String uri;
/// Name of this module, null if there is no name.
String name;
/// Raw data of bindings in this module.
///
/// Elements in this list have the same order where the module/bindings
/// was defined. Elements in this list can only be
/// 1. String
/// 2. BindingInstance
/// If element is String, it may be a
/// a. class name
/// b. another binding variable
/// c. an OpaqueToken
List<Object> directChildren = [];
// All binding instances of this module.
List<BindingInstance> _allBindingInstances;
/// Expands binding information in this module.
List<BindingInstance> getAllBindingInstances(
Map<String, ModuleInfo> allModules) {
if (_allBindingInstances != null) {
return _allBindingInstances;
}
_allBindingInstances = [];
for (var binding in directChildren) {
if (binding is String) {
if (allModules[binding] == null) {
// This is a class or an OpaqueToken.
_allBindingInstances.add(new BindingInstance(binding, binding));
} else {
_allBindingInstances
.addAll(allModules[binding].getAllBindingInstances(allModules));
}
} else if (binding is BindingInstance) {
_allBindingInstances.add(binding);
}
}
return _allBindingInstances;
}
@override
String get expression => name;
}
@@ -0,0 +1,61 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import 'binding_helper.dart';
import 'binding_info.dart';
/// Collects all binding info declared as top level variable.
class BindingVisitor extends RecursiveAstVisitor {
/// Maps module name to module info.
final Map<String, ModuleInfo> _bindingInfo;
/// Maps an internal URI to a public one.
final Map<String, String> _publicUris;
/// All binding variables used as providers in @Component
final Set<String> _bindingVariablesInComponents;
// Uri of Dart file currently visited.
final String _uri;
BindingVisitor(this._uri, this._bindingInfo, this._publicUris,
this._bindingVariablesInComponents);
@override
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
if (node.variables.variables.length != 1) return;
var variable = node.variables.variables[0];
var name = variable.name.name;
if (!_bindingVariablesInComponents.contains(name) &&
!name.endsWith('Bindings') &&
!name.endsWith('Binding') &&
!name.endsWith('Module')) {
return;
}
var initializer = variable.initializer;
if (initializer is! SimpleIdentifier &&
initializer is! PrefixedIdentifier &&
initializer is! ListLiteral &&
initializer is! InstanceCreationExpression &&
initializer is! MethodInvocation) {
return;
}
var module = _bindingInfo.putIfAbsent(name, () => new ModuleInfo());
module.name = name;
module.uri = _publicUris[_uri];
if (initializer is ListLiteral) {
extractBindingInfo(initializer, module);
} else {
processBindingElement(initializer, module);
}
}
}
@@ -0,0 +1,32 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'binding_info.dart';
import 'dart_class_info.dart';
/// Information about a component.
class ComponentInfo {
/// Underlying class.
DartClassInfo classInfo;
/// Selector name, same as tag name.
String selectorName;
/// Template (html) path.
String templatePath;
/// HTML template that is set inline in @Component.
String inlineTemplate;
/// For component types, this is a list of all directive classes used
/// in the component's template.
List<ComponentInfo> templateTypes = [];
/// Value of providers in @Component.
ModuleInfo module;
ComponentInfo(this.classInfo);
}
@@ -0,0 +1,66 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
/// Models a Dart class found during analysis.
class DartClassInfo {
/// Uri of file that contains this Dart class.
String uri;
/// Class name of this class.
String className;
/// The Dart type that this class extends.
///
/// If it doesn't extend anything, this will be 'null'
String extendsType;
final List<ConstructorParameter> constructorParameters = [];
final Map<String, DartClassInfo> memberTypes = {};
final Map<String, List<Annotation>> memberAnnotations = {};
/// A list of Dart types that this class implements.
final List<String> implementsTypes = [];
DartClassInfo(this.className, [this.uri]);
/// Gets or creates a member type entry.
DartClassInfo getMemberType(String memberName) =>
memberTypes.putIfAbsent(memberName, () => null);
@override
String toString() => className == 'dynamic' ? '' : className;
}
/// Parameter used in component constructor.
class ConstructorParameter {
/// Annotations for the parameter.
///
/// Examples include things like @Optional(), @Inject() etc.
List<String> annotations;
/// Parameter's type.
DartClassInfo type;
/// Parameter's name.
///
/// If the parameter is of form 'this.xxx', [name] will xxx since it's what's
/// visible to the caller.
String name;
String _dependency;
/// Dependency to be used when building dependency graph.
///
/// This can be the parameter's type or the @Inject token used.
String get dependency => _dependency ?? type.className;
ConstructorParameter(this.annotations, this.type, this.name,
[this._dependency]);
}
@@ -0,0 +1,256 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import '../app_logger.dart';
import '../exceptions.dart';
import 'dart_class_info.dart';
import 'utils.dart';
/// Visitor to get dependencies of a class.
///
/// Types used in constructor are collected. Type info of some variables in
/// constructor are only available in field definitions, which are collected
/// by visiting field declarations.
class DartClassVisitor extends RecursiveAstVisitor {
/// Maps class name to class info.
final Map<String, DartClassInfo> _dartClasses;
/// Maps an internal URI to a public one.
final Map<String, String> _publicUris;
/// Uri of Dart file currently visited.
String _uri;
DartClassVisitor(this._uri, this._dartClasses, this._publicUris);
/// Visits top level variable to collect OpaqueToken.
///
/// Top level OpaqueToken is treated as dart class. It is something we need
/// to provide bindings.
@override
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
if (node.variables.variables.length != 1) return;
var variable = node.variables.variables[0];
if (variable.initializer is! InstanceCreationExpression) return;
var initializer = variable.initializer as InstanceCreationExpression;
if (extractName(initializer.constructorName.type.name) == 'OpaqueToken') {
var tokenInfo = _getClass(variable.name.name);
tokenInfo.uri = _publicUris[_uri];
}
}
@override
void visitClassDeclaration(ClassDeclaration classDeclaration) {
var classInfo = _getClass(className(classDeclaration));
// Only first appeared class is used to get more accurate matching.
if (classInfo.uri != null) return;
classInfo.uri = _publicUris[_uri];
var extendsClause = classDeclaration.extendsClause;
if (extendsClause != null) {
classInfo.extendsType = extractName(extendsClause.superclass.name);
}
var implementsClause = classDeclaration.implementsClause;
if (implementsClause != null) {
for (var typeName in implementsClause.interfaces) {
classInfo.implementsTypes.add(extractName(typeName.name));
}
}
classDeclaration.visitChildren(this);
}
@override
void visitConstructorDeclaration(ConstructorDeclaration constructor) {
// Only unnamed constructors.
if (constructor.name != null) return;
var classInfo = _classInfo(constructor);
if (classInfo.constructorParameters.isNotEmpty) {
AppLogger.log.fine('Duplicate constructor visit: $constructor');
return;
}
for (var parameter in constructor.parameters.parameters) {
// Annotations like ViewQuery, ViewChild, and ViewChildren
// will use a type like QueryList<SomeComponent>, such dependency can
// be handled by flutter.
var isOptional = parameter.metadata.any((annotation) {
var name = extractName(annotation.name);
return name == 'Optional' || name == 'SkipSelf';
});
if (isOptional) continue;
if (parameter is SimpleFormalParameter ||
parameter is FieldFormalParameter) {
final tokenName = _extractAnnotatedType(parameter.metadata);
final annotations = parameter.metadata
.map((annotation) => annotation.toString())
.toList();
final parameterType = _getParameterType(parameter, classInfo);
classInfo.constructorParameters.add(new ConstructorParameter(
annotations, parameterType, parameter.identifier.name, tokenName));
} else if (parameter is DefaultFormalParameter ||
parameter is FunctionTypedFormalParameter) {
// Defaults are not supported in DI.
} else {
throw new UnsupportedError(
'Unable to handle parameter ${parameter.runtimeType} '
'for ${className(constructor.parent)} in $_uri.');
}
}
}
/// Gets parameter type.
///
/// Different types of parameter can have different ways to extract type.
/// Currently we support [SimpleFormalParameter] and [FieldFormalParameter].
/// When we can't handle the parameter or the type is implicit, we will return
/// [DartClassInfo] object that represents 'dynamic'.
DartClassInfo _getParameterType(
FormalParameter parameter, DartClassInfo classInfo) {
if (parameter is SimpleFormalParameter) {
return _getClassForTypeAnnotation(parameter.type);
} else if (parameter is FieldFormalParameter) {
if (parameter.type != null) {
// Even if this is FieldFormalParameter, it can also have type info.
return _getClassForTypeAnnotation(parameter.type);
} else {
// The actual type will be filled when the corresponding member is
// visited.
return classInfo.getMemberType(parameter.identifier.name);
}
}
return _getClass('dynamic');
}
@override
void visitFieldDeclaration(FieldDeclaration field) {
if (field.parent is! ClassDeclaration) return;
var classInfo = _classInfo(field);
var typeOfField = field.fields.type;
for (var variable in field.fields.variables) {
bool isReferenced = classInfo.memberTypes.containsKey(variable.name.name);
if (typeOfField != null) {
classInfo.memberTypes[variable.name.name] =
_getClass(typeOfField.toString());
} else if (variable.initializer is InstanceCreationExpression) {
classInfo.memberTypes[variable.name.name] =
_getClass(extractConstructorName(variable.initializer));
} else {
classInfo.memberTypes[variable.name.name] = _getClass('dynamic');
}
if (isReferenced) {
for (final parameter in classInfo.constructorParameters) {
if (parameter.name == variable.name.name) {
parameter.type = classInfo.memberTypes[variable.name.name];
}
}
}
classInfo.memberAnnotations[variable.name.name] = field.metadata;
}
}
@override
void visitMethodDeclaration(MethodDeclaration method) {
if (method.parent is! ClassDeclaration) return;
if (!method.isSetter) return;
var classInfo = _classInfo(method);
var parameter = method.parameters.parameters[0];
if (parameter is! SimpleFormalParameter) {
AppLogger.log
.fine('Unimplemented parameter type ${parameter.runtimeType} '
'from $parameter');
return;
}
var setterParameter = parameter as SimpleFormalParameter;
// This member won't be referenced in constructor.
classInfo.memberTypes[method.name.name] =
_getClassForTypeAnnotation(setterParameter.type);
classInfo.memberAnnotations[method.name.name] = method.metadata;
}
/// Pattern to extract generic class.
///
/// For example, for class 'List<InnerType>', the group 1 of this class will
/// be 'InnerType'.
static final RegExp _genericClassPattern =
new RegExp(r'^[a-zA-Z_0-9]*<(.*)>$');
/// Gets [DartClassInfo] for [className].
///
/// If the class is generic, the inner most class's library path will be used,
/// which is more likely to be a special import requirement.
/// There can be cases where [className] is like 'A, B', which comes from the
/// type parameters inside a generic class. For now, we simply treat 'A, B' as
/// a real class.
DartClassInfo _getClass(String className) {
if (!_dartClasses.containsKey(className)) {
final match = _genericClassPattern.firstMatch(className);
if (match != null) {
final innerClass = _getClass(match.group(1));
return _dartClasses[className] =
new DartClassInfo(className, innerClass.uri);
}
_dartClasses[className] = new DartClassInfo(className);
}
return _dartClasses[className];
}
/// Gets [DartClassInfo] for a [TypeAnnotation].
DartClassInfo _getClassForTypeAnnotation(TypeAnnotation type) {
if (type == null) return _getClass('dynamic');
return _getClass(type.toString());
}
DartClassInfo _classInfo(AstNode node) => _getClass(className(node.parent));
String _extractAnnotatedType(NodeList<Annotation> metadata) {
if (metadata == null || metadata.length != 1) return null;
var name = extractName(metadata[0].name);
// Returns annotation as type if there is one.
if (name != 'Inject') return name;
var args = metadata[0].arguments.arguments;
if (args.length != 1) {
throw new InvalidExpressionError('$metadata in $_uri.');
}
var args0 = args[0];
if (args0 is Identifier) {
return extractName(args0);
} else if (args0 is InstanceCreationExpression) {
return extractName(args0.constructorName.type.name);
} else if (args0 is SimpleStringLiteral) {
return args0.value;
}
throw new InvalidExpressionError('$metadata in $_uri.');
}
}
@@ -0,0 +1,122 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import '../app_logger.dart';
import '../exceptions.dart';
import 'binding_helper.dart';
import 'binding_info.dart';
import 'component_info.dart';
import 'dart_class_info.dart';
import 'utils.dart';
/// Visitor to collect value of providers, selector, directives,
/// and templateUrl in @Component or @View.
class FlutterComponentVisitor extends RecursiveAstVisitor {
final Map<String, DartClassInfo> _classes;
final Map<String, ComponentInfo> _components;
// Map to save variables that may be used in @Component.
final Map<String, Expression> _variables = new Map();
FlutterComponentVisitor(this._classes, this._components);
/// Collects top level variables that may be used in @Component.
@override
void visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
if (node.variables.variables.length != 1) return;
var variable = node.variables.variables[0];
_variables[variable.name.name] = variable.initializer;
}
@override
void visitAnnotation(Annotation annotation) {
if (annotation.parent is! ClassDeclaration) return;
var name = annotation.name.name;
if (name == 'Component' || name == 'View') {
visitComponent(annotation);
}
}
void visitComponent(Annotation annotation) {
final name = className(annotation.parent);
final component = _getComponent(name);
for (var arg in annotation.arguments.arguments) {
if (arg is! NamedExpression) return;
var namedExpression = arg as NamedExpression;
var key = namedExpression.name.label.name;
if (key == 'selector') {
component.selectorName = _stringValue(namedExpression.expression);
_components.putIfAbsent(component.classInfo.className, () => component);
} else if (key == 'providers') {
if (namedExpression.expression is ListLiteral) {
component.module = new ModuleInfo();
extractBindingInfo(namedExpression.expression, component.module);
} else if (namedExpression.expression is Identifier) {
component.module = new ModuleInfo();
processBindingElement(namedExpression.expression, component.module);
} else {
throw new InvalidExpressionError(annotation.toString());
}
} else if (key == 'directives') {
_extractDirectives(namedExpression, annotation, component);
} else if (key == 'templateUrl') {
component.templatePath = _stringValue(namedExpression.expression);
} else if (key == 'template') {
component.inlineTemplate = _stringValue(namedExpression.expression);
}
}
}
void _extractDirectives(NamedExpression namedExpression,
Annotation annotation, ComponentInfo component) {
ListLiteral directives;
var value = namedExpression.expression;
if (value is SimpleIdentifier && _variables[value.name] is ListLiteral) {
directives = _variables[value.name];
} else if (value is ListLiteral) {
directives = value;
} else {
AppLogger.log.warning('Cannot parse variable used'
' in directives in $annotation');
return;
}
for (var node in directives.elements) {
if (node is SimpleIdentifier || node is PrefixedIdentifier) {
final templateTypeComponentName = extractName(node);
final templateTypeComponent = _getComponent(templateTypeComponentName);
component.templateTypes.add(templateTypeComponent);
} else {
throw new InvalidExpressionError(annotation.toString());
}
}
}
ComponentInfo _getComponent(String name) {
if (!_components.containsKey(name)) {
if (!_classes.containsKey(name)) {
_classes[name] = new DartClassInfo(name);
}
_components[name] = new ComponentInfo(_classes[name]);
}
return _components[name];
}
String _stringValue(Expression expression) {
String value;
if (expression is SimpleStringLiteral || expression is AdjacentStrings) {
value = (expression as StringLiteral).stringValue;
}
return value;
}
}
@@ -0,0 +1,42 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import '../app_logger.dart';
/// Returns the name of a class declaration.
String className(ClassDeclaration classDeclaration) =>
classDeclaration.name.name;
/// Extracts name from an Identifier.
///
/// Returns last part if [id] is PrefixedIdentifier, otherwise returns name.
String extractName(Identifier id) {
if (id is SimpleIdentifier) {
return id.name;
} else if (id is PrefixedIdentifier) {
return id.identifier.name;
} else {
AppLogger.log.fine('Unsupported Identifier ${id.runtimeType}');
return id.name;
}
}
/// Gets name of the constructor (also the class name).
String extractConstructorName(InstanceCreationExpression instanceCreationExp) {
var constructorId = instanceCreationExp.constructorName.type.name;
var constructorName;
// Work around an issue that constructorName.type.name will
// return fixed for new Clock.fixed.
if (constructorId is PrefixedIdentifier) {
constructorName = constructorId.prefix.name;
} else {
constructorName = constructorId.name;
}
return constructorName;
}
@@ -0,0 +1,90 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
import 'package:analyzer/analyzer.dart';
import 'package:path/path.dart' as path;
import '../app_logger.dart';
import '../path_util.dart';
import 'ast_cache.dart';
/// Used to make analyzing Dart files easier by treating libraries and
/// parts as one unit.
Map<String, T> visitUris<T>(
AstCache asts, AstVisitor visitorFn(String uri, Map<String, T> out)) {
var out = <String, T>{};
for (var uri in asts.allUris) {
var visitor = visitorFn(uri, out);
new _LibraryAndParts(asts, uri).accept(visitor);
}
return out;
}
class _LibraryAndParts {
/// Maps URI to parts of this file.
static final Map<String, List<String>> _uriParts = {};
final AstCache _asts;
final String _uri;
_LibraryAndParts(this._asts, this._uri);
void accept(AstVisitor visitor) {
var compilationUnit = _asts.getCompilationUnit(_uri);
var isLibraryVisitor = new _IsLibraryVisitor();
compilationUnit.accept(isLibraryVisitor);
if (!isLibraryVisitor.isLibrary) return;
compilationUnit.accept(visitor);
var parts = _uriParts.putIfAbsent(_uri, () {
var partVisitor = new _PartVisitor();
compilationUnit.accept(partVisitor);
return partVisitor.parts;
});
var directoryName = path.posix.dirname(getPath(_uri));
var packageName = getPackageName(_uri);
for (var partName in parts) {
var referencedFile =
path.posix.normalize(path.posix.join(directoryName, partName));
var partUri = 'package:${path.posix.join(packageName, referencedFile)}';
_asts.publicUris[partUri] = _uri;
try {
var partCompilationUnit = _asts.getCompilationUnit(partUri);
partCompilationUnit.accept(visitor);
} catch (e) {
AppLogger.log.fine('Failed to parse $partUri: $e');
}
}
}
}
/// Collects 'part' names from Dart files.
class _PartVisitor extends RecursiveAstVisitor {
var parts = <String>[];
@override
visitPartDirective(PartDirective directive) {
parts.add(directive.uri.stringValue);
}
}
/// Visits Dart files and sets a member field if the file is a
/// library (not part of a library).
class _IsLibraryVisitor extends RecursiveAstVisitor {
bool isLibrary = true;
@override
visitPartOfDirective(_) {
isLibrary = false;
}
}
@@ -0,0 +1,16 @@
{{#pageObjectData.commonDependencies}}
import '{{{.}}}';
{{/pageObjectData.commonDependencies}}
class {{poClassName}} {
{{#pageObjectData.variables}}
{{{internalString}}}
{{/pageObjectData.variables}}
{{#pageObjectData.variables}}
{{{getterString}}}
{{/pageObjectData.variables}}
}
@@ -0,0 +1,37 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Exceptions to above rules.
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: ec1044a8773e31b4630bf162d9c374236ad1eaaf
channel: master
project_type: app
@@ -0,0 +1,16 @@
# {{name}}
{{description}}
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -0,0 +1,17 @@
analyzer:
exclude: [build/**]
errors:
uri_has_not_been_generated: ignore
plugins:
- flutter
# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
linter:
rules:
- cancel_subscriptions
- hash_and_equals
- iterable_contains_unrelated_type
- list_remove_unrelated_type
- test_types_in_equals
- unrelated_type_equality_checks
- valid_regexps
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';
import 'package:{{{name}}}/ui/home/screen.dart' as ng;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '{{name}}',
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: ng.HomeScreen(),
);
}
}
@@ -0,0 +1,17 @@
name: {{name}}
description: {{description}}
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_project/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="{{description}}">
<!-- iOS meta tags & icons -->
<link rel="icon" type="image/png" href="favicon.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_project">
<link rel="apple-touch-icon" href="/icons/Icon-192.png">
<title>{{name}}</title>
<link rel="manifest" href="/manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
@@ -0,0 +1,23 @@
{
"name": "{{name}}",
"short_name": "{{name}}",
"start_url": ".",
"display": "minimal-ui",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "{{description}}",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
@@ -0,0 +1,43 @@
@Tags(const ['{{tag}}'])
@TestOn('browser')
import 'package:flutter/flutter.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test/test.dart';
import '{{{projectModel.componentClassUri}}}';
{{#projectModel.referencedUris}}
import '{{{.}}}';
{{/projectModel.referencedUris}}
import '{{{poGenerator.poFileName}}}';
@FlutterEntrypoint()
void main() {
final testBed = new NgTestBed<{{projectModel.componentClassName}}>();
{{#projectModel.needProviders}}
testBed.addProviders([
// Please add provider for following classes.
// For example, if Window is used in your component and you want to
// mock it in test:
// const Provider(Window, useClass: MockWindow)
{{#projectModel.serviceClasses}}
{{.}},
{{/projectModel.serviceClasses}}
]);
{{/projectModel.needProviders}}
NgTestFixture<{{projectModel.componentClassName}}> fixture;
{{poGenerator.poClassName}} pageObject;
setUp(() async {
fixture = await testBed.create();
pageObject = await fixture.resolvePageObject({{poGenerator.poClassName}});
});
tearDown(disposeAnyRunningTest);
test('Default greeting', () async {
// Change it: Check content of the page using pageObject.
expect(pageObject, isNotNull);
});
}
@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text('My Home Page'),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
@@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class {{className}} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
}
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017 Google Inc.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
/// Helper classes for extracting info from Dart files.
export 'src/visitors/flutter_component_visitor.dart';
export 'src/visitors/ast_cache.dart';
export 'src/visitors/binding_helper.dart';
export 'src/visitors/binding_info.dart';
export 'src/visitors/binding_visitor.dart';
export 'src/visitors/component_info.dart';
export 'src/visitors/dart_class_info.dart';
export 'src/visitors/dart_class_visitor.dart';
export 'src/visitors/utils.dart';
export 'src/visitors/visit_resources.dart';