Dart
This commit is contained in:
James Clancey 2018-08-09 17:29:24 -07:00 коммит произвёл Mark Probst
Родитель e6098a7280
Коммит fb328a75b4
10 изменённых файлов: 543 добавлений и 8 удалений

Просмотреть файл

@ -1,15 +1,20 @@
steps:
- command: "FIXTURE=cplusplus,schema-cplusplus,kotlin,schema-kotlin,graphql .buildkite/build-pr.sh"
- command:
"FIXTURE=cplusplus,schema-cplusplus,kotlin,schema-kotlin,graphql .buildkite/build-pr.sh"
label: "C++ Kotlin GraphQL"
- command: "FIXTURE=java,schema-java,schema-json-csharp .buildkite/build-pr.sh"
- command:
"FIXTURE=java,schema-java,schema-json-csharp .buildkite/build-pr.sh"
label: "java schema-json-c#"
- command: "FIXTURE=typescript,schema-typescript,javascript,schema-javascript,flow,schema-flow,json-ts-csharp,python,schema-python .buildkite/build-pr.sh"
- command:
"FIXTURE=typescript,schema-typescript,javascript,schema-javascript,flow,schema-flow,json-ts-csharp,python,schema-python .buildkite/build-pr.sh"
label: "typescript javascript flow python"
- command: "FIXTURE=swift,schema-swift,rust,schema-rust,elm,schema-elm .buildkite/build-pr.sh"
label: "swift rust elm"
- command:
"FIXTURE=swift,schema-swift,rust,schema-rust,elm,schema-elm,dart,schema-dart .buildkite/build-pr.sh"
label: "swift rust elm dart"
- command: "FIXTURE=csharp,schema-csharp,ruby,schema-ruby,golang,schema-golang .buildkite/build-pr.sh"
label: "csharp ruby golang"
- command:
"FIXTURE=csharp,schema-csharp,ruby,schema-ruby,golang,schema-golang .buildkite/build-pr.sh"
label: "csharp ruby golang"

Просмотреть файл

@ -58,6 +58,13 @@ RUN apt-get install python3.6 --assume-yes
RUN curl https://bootstrap.pypa.io/get-pip.py | python3.6
RUN pip3.6 install mypy python-dateutil
# Dart
RUN apt-get install apt-transport-https
RUN sh -c 'curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -'
RUN sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list > /etc/apt/sources.list.d/dart_stable.list'
RUN apt-get update
RUN apt-get install dart
ENV PATH="${workdir}/node_modules/.bin:${PATH}"
COPY . .

Просмотреть файл

@ -15,6 +15,7 @@ import { ElmTargetLanguage } from "./Elm";
import { JSONSchemaTargetLanguage } from "./JSONSchema";
import { RustTargetLanguage } from "./Rust";
import { RubyTargetLanguage } from "./ruby";
import { DartTargetLanguage } from "./Dart";
import { PythonTargetLanguage } from "./Python";
export const all: TargetLanguage[] = [
@ -32,6 +33,7 @@ export const all: TargetLanguage[] = [
new ElmTargetLanguage(),
new JSONSchemaTargetLanguage(),
new RubyTargetLanguage(),
new DartTargetLanguage(),
new PythonTargetLanguage("Python", ["python", "py"], "py")
];

Просмотреть файл

@ -0,0 +1,487 @@
import { Type, EnumType, UnionType, ClassType, ClassProperty } from "../Type";
import { matchType, nullableFromUnion, directlyReachableSingleNamedType } from "../TypeUtils";
import { Sourcelike, maybeAnnotated, modifySource } from "../Source";
import {
utf16LegalizeCharacters,
escapeNonPrintableMapper,
utf16ConcatMap,
standardUnicodeHexEscape,
isAscii,
isLetter,
isDigit,
splitIntoWords,
combineWords,
allUpperWordStyle,
firstUpperWordStyle,
allLowerWordStyle,
isPrintable,
decapitalize
} from "../support/Strings";
import { Name, Namer, funPrefixNamer, DependencyName } from "../Naming";
import { ConvenienceRenderer, ForbiddenWordsInfo } from "../ConvenienceRenderer";
import { TargetLanguage } from "../TargetLanguage";
import { Option, BooleanOption, getOptionValues, OptionValues } from "../RendererOptions";
import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation";
import { defined } from "../support/Support";
import { RenderContext } from "../Renderer";
import { arrayIntercalate } from "../../../node_modules/collection-utils";
import { pythonOptions } from "./Python";
export const dartOptions = {
justTypes: new BooleanOption("just-types", "Types only", false)
};
export class DartTargetLanguage extends TargetLanguage {
constructor() {
super("Dart", ["dart"], "dart");
}
protected getOptions(): Option<any>[] {
return [pythonOptions.justTypes];
}
get supportsUnionsWithBothNumberTypes(): boolean {
return true;
}
protected makeRenderer(renderContext: RenderContext, untypedOptionValues: { [name: string]: any }): DartRenderer {
const options = getOptionValues(dartOptions, untypedOptionValues);
return new DartRenderer(this, renderContext, options);
}
}
const keywords = [
"abstract",
"do",
"import",
"super",
"as",
"dynamic",
"in",
"switch",
"assert",
"else",
"interface",
"sync*",
"async",
"enum",
"is",
"this",
"async*",
"export",
"library",
"throw",
"await",
"external",
"mixin",
"true",
"break",
"extends",
"new",
"try",
"case",
"factory",
"null",
"typedef",
"catch",
"false",
"operator",
"var",
"class",
"final",
"part",
"void",
"const",
"finally",
"rethrow",
"while",
"continue",
"for",
"return",
"with",
"covariant",
"get",
"set",
"yield",
"default",
"if",
"static",
"yield*",
"deferred",
"implements",
"int",
"double",
"bool",
"Map",
"List",
"String",
"File",
"fromJson",
"toJson"
];
const typeNamingFunction = funPrefixNamer("types", n => dartNameStyle(true, false, n));
const propertyNamingFunction = funPrefixNamer("properties", n => dartNameStyle(false, false, n));
const enumCaseNamingFunction = funPrefixNamer("enum-cases", n => dartNameStyle(true, true, n));
// Escape the dollar sign, which is used in string interpolation
const stringEscape = utf16ConcatMap(
escapeNonPrintableMapper(cp => isPrintable(cp) && cp !== 0x24, standardUnicodeHexEscape)
);
function isStartCharacter(codePoint: number): boolean {
if (codePoint === 0x5f) return false; // underscore
return isAscii(codePoint) && isLetter(codePoint);
}
function isPartCharacter(codePoint: number): boolean {
return isStartCharacter(codePoint) || (isAscii(codePoint) && isDigit(codePoint));
}
const legalizeName = utf16LegalizeCharacters(isPartCharacter);
// FIXME: Handle acronyms consistently. In particular, that means that
// we have to use namers to produce the getter and setter names - we can't
// just capitalize and concatenate.
// https://stackoverflow.com/questions/8277355/naming-convention-for-upper-case-abbreviations
function dartNameStyle(startWithUpper: boolean, upperUnderscore: boolean, original: string): string {
const words = splitIntoWords(original);
const firstWordStyle = upperUnderscore
? allUpperWordStyle
: startWithUpper
? firstUpperWordStyle
: allLowerWordStyle;
const restWordStyle = upperUnderscore ? allUpperWordStyle : firstUpperWordStyle;
return combineWords(
words,
legalizeName,
firstWordStyle,
restWordStyle,
firstWordStyle,
restWordStyle,
upperUnderscore ? "_" : "",
isStartCharacter
);
}
type TopLevelDependents = {
encoder: Name;
decoder: Name;
};
export class DartRenderer extends ConvenienceRenderer {
private readonly _gettersAndSettersForPropertyName = new Map<Name, [Name, Name]>();
private _needEnumValues = false;
private readonly _topLevelDependents = new Map<Name, TopLevelDependents>();
private readonly _enumValues = new Map<EnumType, Name>();
constructor(
targetLanguage: TargetLanguage,
renderContext: RenderContext,
private readonly _options: OptionValues<typeof dartOptions>
) {
super(targetLanguage, renderContext);
}
protected forbiddenNamesForGlobalNamespace(): string[] {
return keywords;
}
protected forbiddenForObjectProperties(_c: ClassType, _className: Name): ForbiddenWordsInfo {
return { names: [], includeGlobalForbidden: true };
}
protected makeNamedTypeNamer(): Namer {
return typeNamingFunction;
}
protected namerForObjectProperty(): Namer {
return propertyNamingFunction;
}
protected makeUnionMemberNamer(): Namer {
return propertyNamingFunction;
}
protected makeEnumCaseNamer(): Namer {
return enumCaseNamingFunction;
}
protected unionNeedsName(u: UnionType): boolean {
return nullableFromUnion(u) === null;
}
protected namedTypeToNameForTopLevel(type: Type): Type | undefined {
// If the top-level type doesn't contain any classes or unions
// we have to define a class just for the `FromJson` method, in
// emitFromJsonForTopLevel.
return directlyReachableSingleNamedType(type);
}
protected makeTopLevelDependencyNames(_t: Type, name: Name): DependencyName[] {
const encoder = new DependencyName(propertyNamingFunction, name.order, lookup => `${lookup(name)}_to_json`);
const decoder = new DependencyName(propertyNamingFunction, name.order, lookup => `${lookup(name)}_from_json`);
this._topLevelDependents.set(name, { encoder, decoder });
return [encoder, decoder];
}
protected makeNamesForPropertyGetterAndSetter(
_c: ClassType,
_className: Name,
_p: ClassProperty,
_jsonName: string,
name: Name
): [Name, Name] {
const getterName = new DependencyName(propertyNamingFunction, name.order, lookup => `get_${lookup(name)}`);
const setterName = new DependencyName(propertyNamingFunction, name.order, lookup => `set_${lookup(name)}`);
return [getterName, setterName];
}
protected makePropertyDependencyNames(
c: ClassType,
className: Name,
p: ClassProperty,
jsonName: string,
name: Name
): Name[] {
const getterAndSetterNames = this.makeNamesForPropertyGetterAndSetter(c, className, p, jsonName, name);
this._gettersAndSettersForPropertyName.set(name, getterAndSetterNames);
return getterAndSetterNames;
}
protected makeNamedTypeDependencyNames(t: Type, name: Name): DependencyName[] {
if (!(t instanceof EnumType)) return [];
const enumValue = new DependencyName(propertyNamingFunction, name.order, lookup => `${lookup(name)}_values`);
this._enumValues.set(t, enumValue);
return [enumValue];
}
protected emitFileHeader(): void {
if (this.leadingComments !== undefined) {
this.emitCommentLines(this.leadingComments);
}
if (this._options.justTypes) return;
this.emitLine("// To parse this JSON data, do");
this.emitLine("//");
this.forEachTopLevel("none", (_t, name) => {
const { decoder } = defined(this._topLevelDependents.get(name));
this.emitLine("// final ", modifySource(decapitalize, name), " = ", decoder, "(jsonString);");
});
this.ensureBlankLine();
this.emitLine("import 'dart:convert';");
}
protected emitDescriptionBlock(lines: string[]): void {
this.emitCommentLines(lines, " * ", "/**", " */");
}
protected emitBlock(line: Sourcelike, f: () => void): void {
this.emitLine(line, " {");
this.indent(f);
this.emitLine("}");
}
protected dartType(t: Type, withIssues: boolean = false): Sourcelike {
return matchType<Sourcelike>(
t,
_anyType => maybeAnnotated(withIssues, anyTypeIssueAnnotation, "dynamic"),
_nullType => maybeAnnotated(withIssues, nullTypeIssueAnnotation, "dynamic"),
_boolType => "bool",
_integerType => "int",
_doubleType => "double",
_stringType => "String",
arrayType => ["List<", this.dartType(arrayType.items, withIssues), ">"],
classType => this.nameForNamedType(classType),
mapType => ["Map<String, ", this.dartType(mapType.values, withIssues), ">"],
enumType => this.nameForNamedType(enumType),
unionType => {
const maybeNullable = nullableFromUnion(unionType);
if (maybeNullable === null) {
return "dynamic";
}
return this.dartType(maybeNullable, withIssues);
}
);
}
protected mapList(itemType: Sourcelike, list: Sourcelike, mapper: Sourcelike): Sourcelike {
return ["new List<", itemType, ">.from(", list, ".map((x) => ", mapper, "))"];
}
protected mapMap(valueType: Sourcelike, map: Sourcelike, valueMapper: Sourcelike): Sourcelike {
return ["new Map.from(", map, ").map((k, v) => new MapEntry<String, ", valueType, ">(k, ", valueMapper, "))"];
}
protected fromDynamicExpression(t: Type, ...dynamic: Sourcelike[]): Sourcelike {
return matchType<Sourcelike>(
t,
_anyType => dynamic,
_nullType => dynamic, // FIXME: check null
_boolType => dynamic,
_integerType => dynamic,
_doubleType => [dynamic, ".toDouble()"],
_stringType => dynamic,
arrayType =>
this.mapList(this.dartType(arrayType.items), dynamic, this.fromDynamicExpression(arrayType.items, "x")),
classType => [this.nameForNamedType(classType), ".fromJson(", dynamic, ")"],
mapType =>
this.mapMap(this.dartType(mapType.values), dynamic, this.fromDynamicExpression(mapType.values, "v")),
enumType => [defined(this._enumValues.get(enumType)), ".map[", dynamic, "]"],
unionType => {
const maybeNullable = nullableFromUnion(unionType);
if (maybeNullable === null) {
return dynamic;
}
return [dynamic, " == null ? null : ", this.fromDynamicExpression(maybeNullable, dynamic)];
}
);
}
protected toDynamicExpression = (t: Type, ...dynamic: Sourcelike[]): Sourcelike => {
return matchType<Sourcelike>(
t,
_anyType => dynamic,
_nullType => dynamic,
_boolType => dynamic,
_integerType => dynamic,
_doubleType => dynamic,
_stringType => dynamic,
arrayType => this.mapList("dynamic", dynamic, this.toDynamicExpression(arrayType.items, "x")),
_classType => [dynamic, ".toJson()"],
mapType => this.mapMap("dynamic", dynamic, this.toDynamicExpression(mapType.values, "v")),
enumType => [defined(this._enumValues.get(enumType)), ".reverse[", dynamic, "]"],
unionType => {
const maybeNullable = nullableFromUnion(unionType);
if (maybeNullable === null) {
return dynamic;
}
return [dynamic, " == null ? null : ", this.toDynamicExpression(maybeNullable, dynamic)];
}
);
};
protected emitClassDefinition(c: ClassType, className: Name): void {
this.emitDescription(this.descriptionForType(c));
this.emitBlock(["class ", className], () => {
if (c.getProperties().size === 0) {
this.emitLine(className, "();");
} else {
this.forEachClassProperty(c, "none", (name, _, p) => {
this.emitLine(this.dartType(p.type, true), " ", name, ";");
});
this.ensureBlankLine();
this.emitLine(className, "({");
this.indent(() => {
this.forEachClassProperty(c, "none", (name, _, _p) => {
this.emitLine("this.", name, ",");
});
});
this.emitLine("});");
}
if (this._options.justTypes) return;
this.ensureBlankLine();
this.emitLine("factory ", className, ".fromJson(Map<String, dynamic> json) => new ", className, "(");
this.indent(() => {
this.forEachClassProperty(c, "none", (name, jsonName, property) => {
this.emitLine(
name,
": ",
this.fromDynamicExpression(property.type, 'json["', stringEscape(jsonName), '"]'),
","
);
});
});
this.emitLine(");");
this.ensureBlankLine();
this.emitLine("Map<String, dynamic> toJson() => {");
this.indent(() => {
this.forEachClassProperty(c, "none", (name, jsonName, property) => {
this.emitLine(
'"',
stringEscape(jsonName),
'": ',
this.toDynamicExpression(property.type, name),
","
);
});
});
this.emitLine("};");
});
}
protected emitEnumDefinition(e: EnumType, enumName: Name): void {
const caseNames: Sourcelike[] = Array.from(e.cases).map(c => this.nameForEnumCase(e, c));
this.emitDescription(this.descriptionForType(e));
this.emitLine("enum ", enumName, " { ", arrayIntercalate(", ", caseNames), " }");
if (this._options.justTypes) return;
this.ensureBlankLine();
this.emitLine("final ", defined(this._enumValues.get(e)), " = new EnumValues({");
this.indent(() => {
this.forEachEnumCase(e, "none", (name, jsonName, pos) => {
const comma = pos === "first" || pos === "middle" ? "," : [];
this.emitLine('"', stringEscape(jsonName), '": ', enumName, ".", name, comma);
});
});
this.emitLine("});");
this._needEnumValues = true;
}
protected emitEnumValues(): void {
this.ensureBlankLine();
this.emitMultiline(`class EnumValues<T> {
Map<String, T> map;
Map<T, String> reverseMap;
EnumValues(this.map);
Map<T, String> get reverse {
if (reverseMap == null) {
reverseMap = map.map((k, v) => new MapEntry(v, k));
}
return reverseMap;
}
}`);
}
protected emitSourceStructure(): void {
this.emitFileHeader();
if (!this._options.justTypes) {
this.forEachTopLevel("leading-and-interposing", (t, name) => {
const { encoder, decoder } = defined(this._topLevelDependents.get(name));
this.emitBlock([this.dartType(t), " ", decoder, "(String str)"], () => {
this.emitLine("final jsonData = json.decode(str);");
this.emitLine("return ", this.fromDynamicExpression(t, "jsonData"), ";");
});
this.ensureBlankLine();
this.emitBlock(["String ", encoder, "(", this.dartType(t), " data)"], () => {
this.emitLine("final dyn = ", this.toDynamicExpression(t, "data"), ";");
this.emitLine("return json.encode(dyn);");
});
});
}
this.forEachNamedType(
"leading-and-interposing",
(c: ClassType, n: Name) => this.emitClassDefinition(c, n),
(e, n) => this.emitEnumDefinition(e, n),
(_e, _n) => {
// We don't support this yet.
}
);
if (this._needEnumValues) {
this.emitEnumValues();
}
}
}

Просмотреть файл

@ -677,6 +677,7 @@ export const allFixtures: Fixture[] = [
new JSONFixture(languages.FlowLanguage),
new JSONFixture(languages.JavaScriptLanguage),
new JSONFixture(languages.KotlinLanguage),
new JSONFixture(languages.DartLanguage),
new JSONSchemaJSONFixture(languages.CSharpLanguage),
new JSONTypeScriptFixture(languages.CSharpLanguage),
new JSONSchemaFixture(languages.CSharpLanguage),
@ -692,6 +693,7 @@ export const allFixtures: Fixture[] = [
new JSONSchemaFixture(languages.FlowLanguage),
new JSONSchemaFixture(languages.JavaScriptLanguage),
new JSONSchemaFixture(languages.KotlinLanguage),
new JSONSchemaFixture(languages.DartLanguage),
// FIXME: Why are we missing so many language with GraphQL?
new GraphQLFixture(languages.CSharpLanguage),
new GraphQLFixture(languages.JavaLanguage),
@ -702,5 +704,6 @@ export const allFixtures: Fixture[] = [
new GraphQLFixture(languages.ObjectiveCLanguage, true),
new GraphQLFixture(languages.TypeScriptLanguage),
new GraphQLFixture(languages.FlowLanguage),
new GraphQLFixture(languages.JavaScriptLanguage)
new GraphQLFixture(languages.JavaScriptLanguage),
new GraphQLFixture(languages.DartLanguage)
];

11
test/fixtures/dart/parser.dart поставляемый Normal file
Просмотреть файл

@ -0,0 +1,11 @@
import 'dart:io';
import 'TopLevel.dart';
void main(List<String> arguments) async {
final path = arguments[0];
new File(path).readAsString().then((String contents) async {
final topLevel = topLevelFromJson(contents);
final result = topLevelToJson(topLevel);
stdout.write(result);
});
}

Просмотреть файл

@ -659,3 +659,23 @@ export const KotlinLanguage: Language = {
quickTestRendererOptions: [],
sourceFiles: ["src/Language/Kotlin.ts"]
};
export const DartLanguage: Language = {
name: "dart",
base: "test/fixtures/dart",
runCommand(sample: string) {
return `dart parser.dart \"${sample}\"`;
},
diffViaSchema: false,
skipDiffViaSchema: [],
allowMissingNull: true,
features: [],
output: "TopLevel.dart",
topLevel: "TopLevel",
skipJSON: [],
skipSchema: [],
skipMiscJSON: false,
rendererOptions: {},
quickTestRendererOptions: [],
sourceFiles: ["src/Language/Dart.ts"]
};