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
+16
View File
@@ -0,0 +1,16 @@
# Files and directories created by pub
.packages
.pub/
build/
packages
pubspec.lock
# Files generated by dart tools
.dart_tool
doc/api/
# JetBrains IDEs
.idea/
*.iml
*.ipr
*.iws
+6
View File
@@ -0,0 +1,6 @@
language: dart
dart:
- dev
script:
- dartanalyzer --fatal-warnings --fatal-lints .
- pub run test
+5
View File
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"ngflutter"
]
}
+39
View File
@@ -0,0 +1,39 @@
# Changelog
## 0.4.0
- Fixing Bugs Related to dart2native
## 0.3.1
- Added support for Flutter
## 0.1.2
- Added support for `generate directive`
- Added support for `generate pipe`
## 0.1.1
- Fixed issues on Windows.
## 0.1.0+3
- Exported visitors which can be used to extract info from Dart files.
## 0.1.0+2
- Fixed an issue on README.md formatting.
- Enabled travis.
## 0.1.0+1
- Added missing dependencies.
## 0.1.0
- New implementation, support to create project, generate Flutter component and test.
## 0.0.2
- Fixed `scaffold` command so that it uses the correct platform separator. Instead of creating files in the CWD, it creates them in $name/lib and $name/test of the CWD.
## 0.0.1
- Added `scaffold` command, which generates all of the files need for a component. This should be run from an empty folder and provider a selector name like `my-foo-bar`.
+23
View File
@@ -0,0 +1,23 @@
# How to Contribute
We'd love to accept your patches and contributions to this project. There are
just a few small guidelines you need to follow.
## Contributor License Agreement
Contributions to this project must be accompanied by a Contributor License
Agreement. You (or your employer) retain the copyright to your contribution,
this simply gives us permission to use and redistribute your contributions as
part of the project. Head over to <https://cla.developers.google.com/> to see
your current agreements on file or to sign a new one.
You generally only need to submit a CLA once, so if you've already submitted one
(even if it was for a different project), you probably don't need to do it
again.
## Code reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
+28
View File
@@ -0,0 +1,28 @@
Copyright 2017, Google Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+85
View File
@@ -0,0 +1,85 @@
## Flutter CLI
[![Pub Package](https://img.shields.io/pub/v/ngflutter.svg)](https://pub.dartlang.org/packages/ngflutter)
[![Build Status](https://travis-ci.org/google/ngflutter.svg?branch=master)](https://travis-ci.org/google/ngflutter)
A command line interface for [Flutter][webdev_flutter].
It can scaffold a skeleton Flutter project, component, and test with
[page object][page_object].
## Installation
To install:
```bash
pub global activate ngflutter
pub global activate webdev
```
To update:
```bash
pub global activate ngflutter
pub global activate webdev
```
## Usage
```bash
ngflutter help
```
For help on specific command, run `ngflutter help [command name]`
For example:
```bash
ngflutter help generate test
```
will show how to use command `generate test`.
### Generating Flutter project
```bash
ngflutter new project_name
cd project_name
pub get
webdev serve
```
Navigate to `http://localhost:8080` to visit the project you just built.
Command following will assume that you are in the root directory of
the project.
### Generating component
```bash
ngflutter generate component AnotherComponent
```
This command will generate component under folder `lib/`.
You can use option `-p` to change the folder.
### Generating test
```bash
ngflutter generate test lib/app_component.dart
```
Command above will generate 2 files. One is page object file
and the other one is test file.
Test generated is using [flutter_test][pub_flutter_test]
and [test][pub_test] package.
Use command
```bash
pub run build_runner test --fail-on-severe -- -p chrome
```
to run generated test with Chrome.
[webdev_flutter]: https://webdev.dartlang.org/flutter
[page_object]: https://martinfowler.com/bliki/PageObject.html
[pub_flutter_test]: https://pub.dartlang.org/packages/flutter_test
[pub_test]: https://pub.dartlang.org/packages/test
@@ -0,0 +1,3 @@
analyzer:
exclude:
- 'lib/templates/**'
+24
View File
@@ -0,0 +1,24 @@
import 'package:reflected_mustache/mustache.dart';
main() {
var source = '''
import 'package:flutter/material.dart';
class {{className}} extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
);
}
}
''';
var template = new Template(source, name: 'template-filename.html');
final String output = template.renderString(
{'className': 'FlutterWidget'},
);
print(output);
}
+24
View File
@@ -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 'dart:async';
import 'dart:io';
import 'package:ngflutter/src/command_runner.dart';
import 'package:args/command_runner.dart';
Future main(List<String> args) async {
var runner = new NgDartCommanderRunner();
try {
await runner.run(args);
} on UsageException catch (error) {
print(error);
print(runner.usage);
// Exit code 64 indicates a usage error.
Future.wait([stdout.close(), stderr.close()]).then((_) => exit(64));
}
}
@@ -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';
+27
View File
@@ -0,0 +1,27 @@
name: ngflutter
version: 0.5.0
authors:
- Tianfei Zhu <tianfei@google.com>
- Jing Bian <jingbian@google.com>
- Jonah Williams <jonahwilliams@google.com>
- Rody Davis <rody.davis.jr@gmail.com>
homepage: https://github.com/rodydavis/flutter_cli
description: Scaffolding tool for Flutter
environment:
sdk: ">=2.0.0-dev.69.0 <3.0.0"
executables:
ngflutter:
dependencies:
analyzer: ^0.32.4
ansicolor: ^1.0.2
reflected_mustache: ^1.0.11
logging: ^0.11.3+2
path: ^1.6.2
resource: ^2.1.5
args: ^1.5.3
recase: ^3.0.0
html: ^0.14.0+3
dev_dependencies:
test: ^1.3.0
flutter:
sdk: flutter
@@ -0,0 +1,112 @@
// 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 'package:ngflutter/src/file_reader.dart';
import 'package:ngflutter/src/package_uri_resolver.dart';
import 'package:ngflutter/src/visitors/ast_cache.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('AstCache', () {
FileReader.reader = new FileReaderMock();
AstCache asts;
setUp(() {
asts =
new AstCache('package:a/a.dart', new PackageUriResolver('.packages'));
asts.build();
});
test('should only collect correct URIs', () {
var allUris = <String>[];
for (var file in _files) {
allUris.add(file['uri']);
}
expect(asts.allUris, equals(allUris));
});
test('should report correct public URIs', () {
for (var file in _files) {
expect(asts.publicUris[file['uri']], equals(file['public_uri']),
reason: "${file['uri']} does not have correct public URI");
}
});
});
}
var _files = [
{
'uri': 'package:a/a.dart',
'public_uri': 'package:a/a.dart',
'path': path.join('a', 'lib', 'a.dart'),
'content': '''
import 'package:b/b.dart';
import 'a1.dart';
export 'src/a2.dart';
'''
},
{
'uri': 'package:b/b.dart',
'public_uri': 'package:b/b.dart',
'path': path.join('b', 'lib', 'b.dart'),
'content': '''
import 'package:flutter/flutter.dart';
'''
},
{
'uri': 'package:a/a1.dart',
'public_uri': 'package:a/a1.dart',
'path': path.join('a', 'lib', 'a1.dart'),
'content': '''
import 'dart:io';
'''
},
{
'uri': 'package:a/src/a2.dart',
'public_uri': 'package:a/a.dart',
'path': path.join('a', 'lib', 'src', 'a2.dart'),
'content': '''
import 'a3.dart';
export 'a4.dart';
'''
},
{
'uri': 'package:a/src/a3.dart',
'public_uri': 'package:a/src/a3.dart',
'path': path.join('a', 'lib', 'src', 'a3.dart'),
'content': '''
import 'package:flutter/flutter.dart';
'''
},
{
'uri': 'package:a/src/a4.dart',
'public_uri': 'package:a/a.dart',
'path': path.join('a', 'lib', 'src', 'a4.dart'),
'content': '''
import 'package:flutter/flutter.dart';
'''
}
];
var _dotPackages = ['a:a/lib/', 'b:b/lib/'];
class FileReaderMock implements FileReader {
@override
List<String> readAsLines(String filePath, {Encoding encoding: utf8}) {
if (filePath == '.packages') return _dotPackages;
return null;
}
@override
String readAsString(String filePath, {Encoding encoding: utf8}) {
for (var file in _files) {
if (file['path'] == filePath) return file['content'];
}
return null;
}
}
@@ -0,0 +1,222 @@
// 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:ngflutter/src/visitors/binding_helper.dart';
import 'package:ngflutter/src/visitors/binding_info.dart';
import 'package:test/test.dart';
void main() {
group('TestBedBindingVisitor', () {
_BindingVisitorForTest visitor;
setUp(() {
visitor = new _BindingVisitorForTest();
});
parse(String contents) {
parseCompilationUnit(contents).accept(visitor);
}
test('should parse simple binding', () {
parse('const x = A;');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren, equals(['A']));
});
test('should parse list bindings', () {
parse('const x = [p.A, B];');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren, equals(['B']));
});
test('should parse "const Provider(A)"', () {
parse('const x = const Provider(A);');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, isEmpty);
});
test('should parse "const Provider(const A())"', () {
parse('const x = const Provider(const A());');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, isEmpty);
});
test('should throw error for "const Provider([A])"', () {
expect(() => parse('const x = const Provider([A]);'),
throwsUnsupportedError);
});
test('should parse "provide(A, useClass: B)"', () {
parse('dynamic x = provide(A, useClass: B);');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
});
test('should parse "provide(const A(), useExisting: B)"', () {
parse('dynamic x = provide(const A(), useExisting: B);');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
});
// May need to add support for this scenario.
test('should throw error for "provide(A(), toAlias: B)"', () {
expect(() => parse('dynamic x = provide(A(), toAlias: B);'),
throwsUnsupportedError);
});
test('should parse const Provider(A, useClass: B)', () {
var bindingStr = 'const Provider(A, useClass: B)';
parse('const x = $bindingStr;');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
expect(binding.creationExpression, bindingStr);
});
test('should parse const Provider(A, useFactory: f, deps: const [B, C])',
() {
var bindingStr = 'const Provider(A, useFactory: f, deps: const [B, C])';
parse('const x = $bindingStr;');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B', 'C'])));
expect(binding.creationExpression, bindingStr);
});
test('should parse const Provider(A, useValue: new B())', () {
parse('const x = const Provider(A, useValue: new B());');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
});
test("should parse const Provider('someThing', useValue: new B())", () {
parse("const x = const Provider('someThing', useValue: new B());");
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'someThing');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
});
test('should parse list in deps', () {
parse('''
const x = const Provider(A,
useFactory: f,
deps: const [ const [B, const C()]]);
''');
expect(visitor.modules['x'], isNotNull);
expect(visitor.modules['x'].directChildren.length, 1);
var binding = visitor.modules['x'].directChildren[0] as BindingInstance;
expect(binding.className, 'A');
expect(binding.referencedClasses, equals(new Set.from(['B'])));
});
});
group('ModuleInfo', () {
_BindingVisitorForTest visitor;
setUp(() {
visitor = new _BindingVisitorForTest();
});
parse(String contents) {
parseCompilationUnit(contents).accept(visitor);
}
void checkExpandedModule(
List<BindingInstance> allBindingInstances, List<String> expected) {
expect(allBindingInstances, isNotNull);
var actual = [];
for (var binding in allBindingInstances) {
actual.add(binding.className);
}
expect(actual, equals(expected));
}
test('should expand bindings', () {
parse('''
const a = A;
const b = [
a,
B1,
const Provider(B2, useClass: X)
];
dynamic c = [
provide(C, useClass: X),
b
];
''');
expect(visitor.modules['a'], isNotNull);
expect(visitor.modules['b'], isNotNull);
expect(visitor.modules['c'], isNotNull);
checkExpandedModule(
visitor.modules['a'].getAllBindingInstances(visitor.modules), ['A']);
checkExpandedModule(
visitor.modules['b'].getAllBindingInstances(visitor.modules),
['A', 'B1', 'B2']);
checkExpandedModule(
visitor.modules['c'].getAllBindingInstances(visitor.modules),
['C', 'A', 'B1', 'B2']);
});
});
}
class _BindingVisitorForTest extends RecursiveAstVisitor {
Map<String, ModuleInfo> modules = {};
_BindingVisitorForTest();
@override
visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
var variable = node.variables.variables[0];
var name = variable.name.name;
var initializer = variable.initializer;
var module = modules.putIfAbsent(name, () => new ModuleInfo());
module.name = name;
if (initializer is ListLiteral) {
extractBindingInfo(initializer, module);
} else {
processBindingElement(initializer, module);
}
}
}
@@ -0,0 +1,58 @@
// 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:ngflutter/src/visitors/binding_info.dart';
import 'package:ngflutter/src/visitors/binding_visitor.dart';
import 'package:test/test.dart';
void main() {
group('BindingVisitor', () {
Map<String, ModuleInfo> visit(String content) {
var compilationUnit = parseCompilationUnit(content);
var out = <String, ModuleInfo>{};
var visitor = new BindingVisitor('', out, {}, new Set<String>());
compilationUnit.accept(visitor);
return out;
}
test('should skip misc variable', () {
var module = visit('''
library a;
const xyz = const [A, B, C];
''')['xyz'];
expect(module, isNull);
});
test('should skip empty initializer', () {
var module = visit('''
library a;
dynamic aModule;
''')['aModule'];
expect(module, isNull);
});
test('should parse list bindings', () {
var results = visit('''
library a;
const testBinding = const [A, B, C];
const testModule = D;
const someBindings = testModule;
''');
ModuleInfo module = results['testBinding'];
expect(module, isNotNull);
expect(module.directChildren, equals(['A', 'B', 'C']));
module = results['testModule'];
expect(module, isNotNull);
expect(module.directChildren, equals(['D']));
module = results['someBindings'];
expect(module, isNotNull);
expect(module.directChildren, equals(['testModule']));
});
});
}
@@ -0,0 +1,237 @@
// 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:ngflutter/src/visitors/dart_class_info.dart';
import 'package:ngflutter/src/visitors/dart_class_visitor.dart';
import 'package:test/test.dart';
void main() {
group('DartClassVisitor', () {
Map<String, DartClassInfo> visit(String content) {
var compilationUnit = parseCompilationUnit(content);
var out = <String, DartClassInfo>{};
var visitor = new DartClassVisitor('', out, {});
compilationUnit.accept(visitor);
return out;
}
test('should collect OpaqueToken', () {
var classInfo =
visit("const token = const OpaqueToken('token');")['token'];
expect(classInfo, isNotNull);
});
test('should collect the components constructor types', () {
var classInfo = visit("""
library x;
class Cons {
Cons(String x, Exotic y);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['String', 'Exotic']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y']));
});
test('should collect constructor types which reference this', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(this.y, Exotic z);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['String', 'Exotic']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['y', 'z']));
});
test('should collect member type for field declaration', () {
var classInfo = visit("""
class Cons {
final x = new Clock.fixed();
List<SomeThing> y;
}
""")['Cons'];
expect(classInfo.memberTypes['x'].className, equals('Clock'));
expect(classInfo.memberTypes['y'].className, equals('List<SomeThing>'));
});
test(
'should collect constructor types which reference this'
' and defined after constructor', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(this.x, this.y, List<Exotic> z);
SomeClass x;
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['SomeClass', 'String', 'List<Exotic>']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y', 'z']));
});
test('should collect classes types with implicit constructors', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
}
""")['Cons'];
expect(classInfo.constructorParameters, equals([]));
});
test('shoulde collect extends clauses', () {
var classInfo = visit("""
library x;
class Cons extends SuperAwesomeBase {
}
""")['Cons'];
expect(classInfo.extendsType, equals('SuperAwesomeBase'));
});
test('should collect implements clauses', () {
var classInfo = visit("""
library x;
class Cons implements dull.DullInterface, AwesomeInterface {
}
""")['Cons'];
expect(classInfo.implementsTypes,
equals(['DullInterface', 'AwesomeInterface']));
});
test('should skip optional parameter', () {
var classInfo = visit("""
library x;
class Cons {
Cons(@Optional() String x, @SkipSelf() Exotic y);
}
""")['Cons'];
expect(classInfo.constructorParameters.isEmpty, true);
});
test('should get type from @Inject(MyString)', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(@Inject(MyString) String x, @Inject(YString) this.y);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['MyString', 'YString']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y']));
});
test("should get type from @Inject('someString')", () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(@Inject('someString') String x, @Inject(YString) this.y);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['someString', 'YString']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y']));
});
test('should get type from @Inject(const MyString())', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(@Inject(const MyString()) String x,
@Inject(const YString()) this.y);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['MyString', 'YString']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y']));
});
test('should get type from @MyString()', () {
var classInfo = visit("""
library x;
class Cons {
final String y;
Cons(@MyString String x,
@YString this.y);
}
""")['Cons'];
expect(
classInfo.constructorParameters
.map((parameter) => parameter.dependency),
equals(['MyString', 'YString']));
expect(classInfo.constructorParameters.map((parameter) => parameter.name),
equals(['x', 'y']));
});
test('should get member type for setter', () {
var classInfo = visit("""
class Cons {
var x;
var y;
var z;
Cons();
set x(String value){x = value;}
set y(value){y = value;}
set z(List<String> value){z = value;}
}
""")['Cons'];
expect(classInfo.memberTypes['x'].className, equals('String'));
expect(classInfo.memberTypes['y'].className, equals('dynamic'));
expect(classInfo.memberTypes['z'].className, equals('List<String>'));
});
});
}
@@ -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 'package:recase/recase.dart';
import 'package:test/test.dart';
void main() {
group('Entity name', () {
test('should produce correct formats of names', () {
final name = new ReCase('abc_bcd_cde');
expect(name.titleCase, 'Abc Bcd Cde');
expect(name.camelCase, 'AbcBcdCde');
expect(name.camelCase.toLowerCase(), 'abcBcdCde');
expect(name.paramCase, 'abc-bcd-cde');
expect(name.snakeCase, 'abc_bcd_cde');
});
test('should be handle to handle different types of input', () {
final camelCasedName1 = new ReCase('AbcBcdCde');
expect(camelCasedName1.snakeCase, 'abc_bcd_cde');
final camelCasedName2 = new ReCase('abcBcdCde');
expect(camelCasedName2.snakeCase, 'abc_bcd_cde');
final dashedName = new ReCase('abc-bcd-cde');
expect(dashedName.snakeCase, 'abc_bcd_cde');
});
test('should throw for incorrect formats', () {
expect(() => new ReCase('Abc-bcd'), throwsArgumentError);
expect(() => new ReCase('abc-bcd_cde'), throwsArgumentError);
expect(() => new ReCase('_abc'), throwsArgumentError);
});
});
}
@@ -0,0 +1,161 @@
// 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:ngflutter/src/visitors/flutter_component_visitor.dart';
import 'package:ngflutter/src/visitors/component_info.dart';
import 'package:ngflutter/src/visitors/dart_class_info.dart';
import 'package:test/test.dart';
void main() {
group('FlutterComponentVisitor', () {
Map<String, ComponentInfo> visit(
Map<String, DartClassInfo> classes, String content) {
var compilationUnit = parseCompilationUnit(content);
var out = <String, ComponentInfo>{};
var visitor = new FlutterComponentVisitor(classes, out);
compilationUnit.accept(visitor);
return out;
}
test('should parse an Flutter component', () {
final classes = <String, DartClassInfo>{
'A': new DartClassInfo('A'),
'B': new DartClassInfo('B'),
'TestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
library a;
@Component(
selector: 'test',
directives: const [A, B],
templateUrl: 'test.html'
)
class TestComponent {}
''')['TestComponent'];
expect(component, isNotNull);
expect(component.selectorName, equals('test'));
expect(component.templatePath, equals('test.html'));
expect(
component.templateTypes
.map((templateType) => templateType.classInfo.className),
equals(['A', 'B']));
});
test('should collect inline template', () {
final classes = <String, DartClassInfo>{
'TestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
library a;
@Component(
selector: 'test',
template: '<div></div>'
)
class TestComponent {}
''')['TestComponent'];
expect(component, isNotNull);
expect(component.inlineTemplate, equals('<div></div>'));
});
test('can combine component and view tags values', () {
final classes = <String, DartClassInfo>{
'A': new DartClassInfo('A'),
'B': new DartClassInfo('B'),
'TestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
library a;
@Component(
selector: 'test'
)
@View(
directives: const [A, B],
templateUrl: 'test.html'
)
class TestComponent {}
''')['TestComponent'];
expect(component, isNotNull);
expect(component.selectorName, equals('test'));
expect(component.templatePath, equals('test.html'));
expect(
component.templateTypes
.map((templateType) => templateType.classInfo.className),
equals(['A', 'B']));
});
test('can parse directives which value is a variable', () {
final classes = <String, DartClassInfo>{
'A': new DartClassInfo('A'),
'B': new DartClassInfo('B'),
'GtTestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
const myDirectives = const [A, B];
@Component(
selector: 'gt-test',
directives: myDirectives,
templateUrl: 'gt_test.html'
)
class GtTestComponent {}
''')['TestComponent'];
expect(
component.templateTypes
.map((templateType) => templateType.classInfo.className),
equals(['A', 'B']));
});
test('should collect component binding list', () {
final classes = <String, DartClassInfo>{
'A': new DartClassInfo('A'),
'B': new DartClassInfo('B'),
'TestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
library a;
@Component(
providers: const [A, B],
selector: 'test'
)
class TestComponent {}
''')['TestComponent'];
expect(component, isNotNull);
expect(component.module, isNotNull);
expect(component.module.directChildren, equals(['A', 'B']));
});
test('should collect component binding variable', () {
final classes = <String, DartClassInfo>{
'A': new DartClassInfo('A'),
'TestComponent': new DartClassInfo('TestComponent')
};
var component = visit(classes, '''
library a;
@Component(
providers: A,
selector: 'test'
)
class TestComponent {}
''')['TestComponent'];
expect(component, isNotNull);
expect(component.module, isNotNull);
expect(component.module.directChildren, equals(['A']));
});
});
}
@@ -0,0 +1,120 @@
// 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 'package:ngflutter/src/app_logger.dart';
import 'package:ngflutter/src/command_runner.dart';
import 'package:ngflutter/src/file_reader.dart';
import 'package:ngflutter/src/file_writer.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('ngflutter', () {
AppLoggerMock logger;
FileWriterMock writer;
FileReader.reader = new FileReaderMock();
NgDartCommanderRunner runner;
setUp(() {
AppLogger.log = logger = new AppLoggerMock();
FileWriter.writer = writer = new FileWriterMock();
runner = new NgDartCommanderRunner();
});
test('should generate test with default path', () async {
await runner
.run(['generate', 'test', path.join('lib', 'app_component.dart')]);
expect(writer.filesWritten.length, 2);
expect(writer.filesWritten[0].startsWith('test'), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
});
}
class AppLoggerMock implements AppLogger {
int severeCount = 0;
int warningCount = 0;
bool verbose = false;
@override
void fine(message, [Object error, StackTrace stackTrace]) {}
@override
void info(message, [Object error, StackTrace stackTrace]) {}
@override
void severe(message, [Object error, StackTrace stackTrace]) {
++severeCount;
}
@override
void warning(message, [Object error, StackTrace stackTrace]) {
++warningCount;
}
@override
set isVerbose(bool value) {
verbose = value;
}
}
class FileWriterMock implements FileWriter {
List<String> filesWritten = [];
FileWriterMock();
@override
void write(String destination, String content) {
filesWritten.add(destination);
}
}
var _files = [
{
'path': path.join('hello_flutter', 'lib', 'app_component.dart'),
'content': '''
import 'package:flutter/flutter.dart';
@Component(
selector: 'app-component',
templateUrl: 'app_component.html')
class AppComponent {
var name = 'Flutter';
}
'''
},
{
'path': path.join('lib', 'app_component.html'),
'content': '''
<h1>Hello Flutter</h1>
'''
}
];
var _dotPackages = ['hello_flutter:hello_flutter/lib/'];
var _pubSpec = ['name: hello_flutter'];
class FileReaderMock implements FileReader {
@override
List<String> readAsLines(String filePath, {Encoding encoding: utf8}) {
if (filePath == '.packages') {
return _dotPackages;
} else if (filePath == 'pubspec.yaml') {
return _pubSpec;
}
return null;
}
@override
String readAsString(String filePath, {Encoding encoding: utf8}) {
for (var file in _files) {
if (file['path'] == filePath) return file['content'];
}
return null;
}
}
@@ -0,0 +1,175 @@
// 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:ngflutter/src/app_logger.dart';
import 'package:ngflutter/src/command_runner.dart';
import 'package:ngflutter/src/file_writer.dart';
import 'package:ngflutter/src/path_util.dart';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('ngflutter', () {
AppLoggerMock logger;
FileWriterMock writer;
NgDartCommanderRunner runner;
setUp(() {
AppLogger.log = logger = new AppLoggerMock();
FileWriter.writer = writer = new FileWriterMock();
runner = new NgDartCommanderRunner();
});
test('should fix invalid path', () {
expect(getNormalizedPath(r'path/to\some/folder'),
path.join('path', 'to', 'some', 'folder'));
});
test('should generate component with default path', () async {
await runner.run(['generate', 'component', 'HelloWorldComponent']);
expect(writer.filesWritten.length, 2);
expect(writer.filesWritten[0].startsWith('lib'), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate component with specified path', () async {
final componentPath = path.join('some', 'path');
await runner.run([
'generate',
'component',
'--path=$componentPath',
'HelloWorldComponent'
]);
expect(writer.filesWritten.length, 2);
expect(writer.filesWritten[0].startsWith(componentPath), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate project with default path', () async {
final projectPath = path.join('.', 'hello_flutter');
await runner.run(['-v', 'new', 'HelloFlutter']);
expect(logger.verbose, isTrue);
expect(writer.filesWritten.length, 8);
expect(writer.filesWritten[0].startsWith(projectPath), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate project with specified path', () async {
final projectPath = path.join('some', 'path');
await runner.run(['new', 'HelloFlutter', '-p $projectPath']);
expect(logger.verbose, isFalse);
expect(writer.filesWritten.length, 8);
expect(
writer.filesWritten[0]
.startsWith(path.join(projectPath, 'hello_flutter')),
isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate directive with default path', () async {
await runner.run(['generate', 'directive', 'HelloWorldDirective']);
expect(writer.filesWritten.length, 1);
expect(writer.filesWritten[0].startsWith('lib'), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate directive with specified path', () async {
final directivePath = path.join('some', 'path');
await runner.run([
'generate',
'directive',
'--path=$directivePath',
'HelloWorldDirective'
]);
expect(writer.filesWritten.length, 1);
expect(writer.filesWritten[0].startsWith(directivePath), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate pipe with default path', () async {
await runner.run(['generate', 'pipe', 'HelloWorldPipe']);
expect(writer.filesWritten.length, 1);
expect(writer.filesWritten[0].startsWith('lib'), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should generate pipe with specified path', () async {
final directivePath = path.join('some', 'path');
await runner
.run(['generate', 'pipe', '--path=$directivePath', 'HelloWorldPipe']);
expect(writer.filesWritten.length, 1);
expect(writer.filesWritten[0].startsWith(directivePath), isTrue);
expect(logger.warningCount, 0);
expect(logger.severeCount, 0);
});
test('should throw UsageException for missing project name', () {
expect(runner.run(['new']), throwsA(const TypeMatcher<UsageException>()));
});
test('should throw UsageException for missing component name', () {
expect(runner.run(['generate', 'component']),
throwsA(const TypeMatcher<UsageException>()));
});
test('should throw UsageException for missing directive name', () {
expect(runner.run(['generate', 'directive']),
throwsA(const TypeMatcher<UsageException>()));
});
test('should throw UsageException for missing pipe name', () {
expect(runner.run(['generate', 'pipe']),
throwsA(const TypeMatcher<UsageException>()));
});
});
}
class AppLoggerMock implements AppLogger {
int severeCount = 0;
int warningCount = 0;
bool verbose = false;
@override
void fine(message, [Object error, StackTrace stackTrace]) {}
@override
void info(message, [Object error, StackTrace stackTrace]) {}
@override
void severe(message, [Object error, StackTrace stackTrace]) {
++severeCount;
}
@override
void warning(message, [Object error, StackTrace stackTrace]) {
++warningCount;
}
@override
set isVerbose(bool value) {
verbose = value;
}
}
class FileWriterMock implements FileWriter {
List<String> filesWritten = [];
FileWriterMock();
@override
void write(String destination, String content) {
filesWritten.add(destination);
}
}
@@ -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:convert';
import 'package:ngflutter/src/exceptions.dart';
import 'package:ngflutter/src/file_reader.dart';
import 'package:ngflutter/src/package_uri_resolver.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('PackageUriResolver', () {
FileReader.reader = new FileReaderMock();
PackageUriResolver resolver;
setUp(() {
resolver = new PackageUriResolver('.packages');
});
test('should parse dependent package URI', () {
var filePath = resolver.resolve('package:some_package/some_file.dart');
expect(
filePath,
equals(path.join(
path.separator,
path.join(
'home',
'someone',
'.pub-cache',
'hosted',
'pub.dartlang.org',
'some_package-1.0.0',
'lib',
'some_file.dart'))));
});
test('should parse current project URI', () {
var filePath = resolver.resolve('package:ngflutter/some_file.dart');
expect(filePath, equals(path.join('lib', 'some_file.dart')));
});
test('should throw for unknow package', () {
expect(() => resolver.resolve('package:unknown/some_file.dart'),
throwsA(const TypeMatcher<UsageException>()));
});
});
}
class FileReaderMock implements FileReader {
static const List<String> _dotPackages = const [
'# Generated by pub on 2017-05-01 00:00:00.00001.',
'some_package:file:///home/someone/.pub-cache/hosted/'
'pub.dartlang.org/some_package-1.0.0/lib/',
'ngflutter:lib/'
];
@override
List<String> readAsLines(Object uri, {Encoding encoding: utf8}) {
if (uri is String && uri == '.packages') return _dotPackages;
return null;
}
@override
String readAsString(Object uri, {Encoding encoding: utf8}) => null;
}
@@ -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 'package:ngflutter/src/page_object_data.dart';
import 'package:test/test.dart';
void main() {
group('PageObjectData', () {
test('should generate items.', () {
var po = new PageObjectData(
'<action-button class="good"></action-button>',
);
expect(po.variables.first.getterString,
'Future<PageLoaderElement> get good => _getGood();');
expect(po.variables.first.internalString,
"@ByClass('good')\n Lazy<PageLoaderElement> _getGood;");
expect(po.variables.first.type.uri, 'package:pageloader/objects.dart');
});
test('should generate items in list', () {
var po = new PageObjectData(
'<action-button class="good" *ngFor="xxx"></action-button>',
);
expect(po.variables.first.getterString,
'Future<List<PageLoaderElement>> get good => _getGood();');
expect(po.variables.first.internalString,
"@ByClass('good')\n Lazy<List<PageLoaderElement>> _getGood;");
});
test('should generate items in parents list', () {
var po = new PageObjectData(
'<p *ngFor="xxx"><action-button class="good">'
'</action-button></p>',
);
expect(po.variables.first.getterString,
'Future<List<PageLoaderElement>> get good => _getGood();');
expect(po.variables.first.internalString,
"@ByClass('good')\n Lazy<List<PageLoaderElement>> _getGood;");
});
test('should generate items with default type', () {
var po = new PageObjectData('<some-widget class="cool"></some-widget>');
expect(po.variables.first.getterString,
'Future<PageLoaderElement> get cool => _getCool();');
expect(po.variables.first.internalString,
"@ByClass('cool')\n Lazy<PageLoaderElement> _getCool;");
expect(po.variables.first.type.uri, 'package:pageloader/objects.dart');
});
test('should sort generated items', () {
var po1 = new PageObjectData(
'<action-button class="good"></action-button>'
'<action-button class="bad"></action-button>',
);
expect(po1.variables.first.name, 'Bad');
expect(po1.variables.last.name, 'Good');
var po2 = new PageObjectData(
'<action-button class="good"></action-button>'
'<action-button class="bad"></action-button>',
);
expect(po2.variables.first.name, 'Bad');
expect(po2.variables.last.name, 'Good');
});
test('should ignore some tags.', () {
var po = new PageObjectData('<p>123</p>');
expect(po.variables.isEmpty, true);
});
test('should add optional annotation.', () {
var po = new PageObjectData('<some-widget *ngIf="1"></some-widget>');
expect(po.variables[0].internalString, startsWith('@optional'));
});
test('should add optional annotation when parent is optional.', () {
var po = new PageObjectData(
'<div *ngIf="1"><some-widget></some-widget></div>');
expect(po.variables[0].internalString, startsWith('@optional'));
});
test('should add optional annotation when in <template [ngIf]>', () {
var po = new PageObjectData(
'<template [ngIf]="1"><some-widget></some-widget></template>',
);
expect(po.variables[0].internalString, startsWith('@optional'));
});
test('should choose correct selector.', () {
var po = new PageObjectData(
'<some-widget class="cool"></some-widget>'
'<some-widget class="cool" id="cooler"></some-widget>'
'<some-widget></some-widget>',
);
expect(po.variables.length, 3);
expect(po.variables[0].selector.toString(), "@ByClass('cool')");
expect(po.variables[1].selector.toString(), "@ById('cooler')");
expect(po.variables[2].selector.toString(), "@ByTagName('some-widget')");
});
test('should work with selectors with attributes', () {
var po = new PageObjectData(
'<some-cell class="field-class"></some-cell>'
'<some-cell id="fieldWithId"></some-cell>',
);
expect(po.variables[0].selector.toString(), "@ByClass('field-class')");
expect(po.variables[1].selector.toString(), "@ById('fieldWithId')");
expect(po.variables[0].name, 'FieldClass');
expect(po.variables[1].name, 'FieldWithId');
});
test('should produce correct commonDependencies.', () {
var po1 = new PageObjectData('');
expect(po1.commonDependencies, [PageObjectData.pageLoaderDependency]);
var po3 = new PageObjectData('<some-widget></some-widget>');
expect(po3.commonDependencies, [
PageObjectData.pageLoaderDependency,
PageObjectData.asyncDependency
]);
});
});
}
@@ -0,0 +1,127 @@
// 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 'package:ngflutter/src/file_reader.dart';
import 'package:ngflutter/src/project_model.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
void main() {
group('ProjectModel', () {
FileReader.reader = new FileReaderMock();
ProjectModel projectModel;
setUp(() {
projectModel = new ProjectModel(
'.packages', 'pubspec.yaml', path.join('lib', 'a.dart'), null);
});
test('shoud export project name', () {
expect(projectModel.projectName, equals('a'));
});
test('should export component class URI', () {
expect(projectModel.componentClassUri, equals('package:a/a.dart'));
});
test('should export component class name', () {
expect(projectModel.componentClassName, equals('TestComponent'));
});
test('should export service classes used', () {
expect(projectModel.serviceClasses, equals(['D']));
expect(projectModel.needProviders, isTrue);
expect(projectModel.referencedUris, equals(['package:a/a.dart']));
});
test('should export dart classes.', () {
expect(projectModel.dartClasses.keys.length, equals(5));
expect(projectModel.dartClasses.keys.toList(),
equals(['TestComponent', 'A', 'C', 'D', 'E']));
});
test('should export component classes.', () {
expect(projectModel.components.keys.length, equals(1));
expect(projectModel.components.keys.first, equals('TestComponent'));
});
test('should export binding modules.', () {
expect(projectModel.modules.length, equals(1));
expect(projectModel.modules.keys.first, equals('someThing'));
});
});
}
var _files = [
{
'path': path.join('a', 'lib', 'a.dart'),
'content': '''
library test_a;
import 'package:flutter/flutter.dart';
import 'a1.dart';
part 'src/d.dart';
@Component(
selector: 'test-component',
providers: const [
someThing,
const Provider(A, useClass: B)
],
templateUrl: 'test.html')
class TestComponent {
A _a;
C _c;
D _d;
E _e;
TestComponent(this._a, this._c, this._d, this._e);
}
'''
},
{
'path': path.join('a', 'lib', 'src', 'd.dart'),
'content': '''
part of test_a;
class D{}
'''
},
{
'path': path.join('a', 'lib', 'a1.dart'),
'content': '''
import 'package:flutter/flutter.dart';
const someThing = const [
const Provider(C, useValue: 'test')
];
'''
}
];
var _dotPackages = ['a:a/lib/'];
var _pubSpec = ['name: a'];
class FileReaderMock implements FileReader {
@override
List<String> readAsLines(String filePath, {Encoding encoding: utf8}) {
if (filePath == '.packages') {
return _dotPackages;
} else if (filePath == 'pubspec.yaml') {
return _pubSpec;
}
return null;
}
@override
String readAsString(String filePath, {Encoding encoding: utf8}) {
for (var file in _files) {
if (file['path'] == filePath) return file['content'];
}
return null;
}
}