Skip to content
This repository was archived by the owner on May 25, 2022. It is now read-only.

Commit 1b44e99

Browse files
authored
Merge pull request #24 from davidmorgan/switch-to-oo
Refactor to objects. Fix duplicate detection.
2 parents aafb902 + 3e5e494 commit 1b44e99

15 files changed

+741
-278
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 1.0.0
4+
5+
- API now stable.
6+
- Refactor generator to split into logical classes.
7+
- Fix "watch mode": check for duplicate identifiers per library.
8+
39
## 0.2.2
410

511
- Improve error output on failure to generate.

enum_class/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: enum_class
2-
version: 0.2.2
2+
version: 1.0.0
33
description: >
44
Dart classes as enums. This library is the runtime dependency.
55
authors:

enum_class_generator/lib/enum_class_generator.dart

Lines changed: 3 additions & 250 deletions
Original file line numberDiff line numberDiff line change
@@ -8,265 +8,18 @@ import 'dart:async';
88

99
import 'package:analyzer/dart/element/element.dart';
1010
import 'package:build/build.dart';
11-
import 'package:quiver/iterables.dart' show concat;
11+
import 'package:enum_class_generator/src/source_library.dart';
1212
import 'package:source_gen/source_gen.dart';
1313

1414
/// Generator for Enum Classes.
1515
///
1616
/// See https://github.com/google/enum_class.dart/tree/master/example for how
1717
/// to use it.
1818
class EnumClassGenerator extends Generator {
19-
Set<String> _usedGeneratedIdentifiers = new Set<String>();
20-
2119
@override
2220
Future<String> generate(Element element, BuildStep buildStep) async {
23-
if (element is! ClassElement) {
24-
return null;
25-
}
26-
final classElement = element as ClassElement;
27-
final enumName = classElement.displayName;
28-
29-
if (classElement.supertype.displayName != 'EnumClass') {
30-
// Maybe they're trying to use EnumClass but forgot to import the library.
31-
if (classElement
32-
.computeNode()
33-
.toSource()
34-
.contains('class ${classElement.displayName} extends EnumClass')) {
35-
throw _makeError(
36-
["Import EnumClass: import 'package:enum_class/enum_class.dart';"]);
37-
} else {
38-
return null;
39-
}
40-
}
41-
42-
final libraryName = classElement.library.displayName;
43-
final fields = _getApplicableFields(classElement);
44-
final errors = concat([
45-
_checkPart(classElement),
46-
_checkFields(libraryName, fields),
47-
_checkConstructor(classElement),
48-
_checkValuesGetter(libraryName, classElement),
49-
_checkValueOf(libraryName, classElement)
50-
]).toList();
51-
52-
final mixinElement = classElement.library.getType(enumName + 'Mixin');
53-
final shouldGenerateMixin = mixinElement != null;
54-
if (shouldGenerateMixin) {
55-
final expectedCode =
56-
'abstract class ${enumName}Mixin = Object with _\$${enumName}Mixin;';
57-
if (mixinElement.computeNode().toString() != expectedCode) {
58-
errors.add('Remove mixin or declare using exactly: $expectedCode');
59-
}
60-
}
61-
62-
if (errors.isNotEmpty) {
63-
throw _makeError(errors);
64-
}
65-
66-
return _generateCode(classElement, enumName, fields, shouldGenerateMixin);
67-
}
68-
69-
Iterable<String> _checkPart(ClassElement classElement) {
70-
final fileName =
71-
classElement.library.source.shortName.replaceAll('.dart', '');
72-
final expectedCode = "part '$fileName.g.dart';";
73-
final alternativeExpectedCode = 'part "$fileName.g.dart";';
74-
final source = classElement.library.source.contents.data;
75-
return source.contains(expectedCode) ||
76-
source.contains(alternativeExpectedCode)
77-
? <String>[]
78-
: <String>['Import generated part: $expectedCode'];
79-
}
80-
81-
Iterable<FieldElement> _getApplicableFields(ClassElement classElement) {
82-
final enumName = classElement.displayName;
83-
final result = <FieldElement>[];
84-
for (final field in classElement.fields) {
85-
final type = field.getter.returnType.displayName;
86-
if (!field.isSynthetic && (type == enumName || type == 'dynamic')) {
87-
result.add(field);
88-
}
89-
}
90-
return result;
91-
}
92-
93-
Iterable<String> _checkFields(
94-
String libraryName, Iterable<FieldElement> fields) {
95-
final result = <String>[];
96-
for (final field in fields) {
97-
final fieldName = field.displayName;
98-
if (field.getter.returnType.displayName == 'dynamic') {
99-
result.add('Specify a type for field "$fieldName".');
100-
continue;
101-
} else if (!field.isConst && !field.isStatic) {
102-
result.add('Make field "$fieldName" static const.');
103-
continue;
104-
} else if (!field.isConst) {
105-
result.add('Make field "$fieldName" const.');
106-
continue;
107-
}
108-
109-
if (!field.computeNode().toString().startsWith('$fieldName = _\$')) {
110-
result
111-
.add('Initialize field "$fieldName" with a value starting "_\$".');
112-
}
113-
114-
final identifier = _getGeneratedIdentifier(field);
115-
result.addAll(
116-
_checkAndRegisterGeneratedIdentifier(libraryName, identifier));
117-
}
118-
return result;
119-
}
120-
121-
Iterable<String> _checkConstructor(ClassElement classElement) {
122-
final enumName = classElement.displayName;
123-
final expectedCode = 'const $enumName._(String name) : super(name);';
124-
return classElement.constructors.length == 1 &&
125-
classElement.constructors.single.computeNode().toString() ==
126-
expectedCode
127-
? <String>[]
128-
: <String>['Have exactly one constructor: $expectedCode'];
129-
}
130-
131-
Iterable<String> _checkValuesGetter(
132-
String libraryName, ClassElement classElement) {
133-
final enumName = classElement.displayName;
134-
final valuesIdentifier = _getValuesIdentifier(classElement, enumName);
135-
final result = <String>[];
136-
if (valuesIdentifier == null) {
137-
result.add(
138-
'Add getter: static BuiltSet<$enumName> get values => _\$values');
139-
} else {
140-
result.addAll(
141-
_checkAndRegisterGeneratedIdentifier(libraryName, valuesIdentifier));
142-
}
143-
return result;
144-
}
145-
146-
Iterable<String> _checkValueOf(
147-
String libraryName, ClassElement classElement) {
148-
final enumName = classElement.displayName;
149-
final valueOfIdentifier = _getValueOfIdentifier(classElement, enumName);
150-
final result = <String>[];
151-
if (valueOfIdentifier == null) {
152-
result.add('Add method: '
153-
'static $enumName valueOf(String name) => _\$valueOf(name)');
154-
} else {
155-
result.addAll(
156-
_checkAndRegisterGeneratedIdentifier(libraryName, valueOfIdentifier));
157-
}
158-
return result;
159-
}
160-
161-
Iterable<String> _checkAndRegisterGeneratedIdentifier(
162-
String libraryName, String identifier) {
163-
final result = <String>[];
164-
final scopedIdentifier = '$libraryName.$identifier';
165-
if (_usedGeneratedIdentifiers.contains(scopedIdentifier)) {
166-
result
167-
.add('Generated identifier "_\$$identifier" is used multiple times in'
168-
' $libraryName, change to something else.');
169-
} else {
170-
_usedGeneratedIdentifiers.add(scopedIdentifier);
171-
}
172-
return result;
173-
}
174-
175-
String _generateCode(ClassElement classElement, String enumName,
176-
Iterable<FieldElement> fields, bool generateMixin) {
177-
final result = new StringBuffer();
178-
179-
for (final field in fields) {
180-
final fieldName = field.displayName;
181-
result.writeln('const $enumName _\$${_getGeneratedIdentifier(field)} = '
182-
'const $enumName._(\'$fieldName\');');
183-
}
184-
185-
result.writeln('');
186-
187-
final valueOf = _getValueOfIdentifier(classElement, enumName);
188-
result.writeln('$enumName _\$$valueOf(String name) {'
189-
'switch (name) {');
190-
for (final field in fields) {
191-
final fieldName = field.displayName;
192-
result.writeln(
193-
'case \'$fieldName\': return _\$${_getGeneratedIdentifier(field)};');
194-
}
195-
result.writeln('default: throw new ArgumentError(name);');
196-
result.writeln('}}');
197-
198-
result.writeln('');
199-
200-
final values = _getValuesIdentifier(classElement, enumName);
201-
result.writeln('final BuiltSet<$enumName> _\$$values ='
202-
'new BuiltSet<$enumName>(const [');
203-
for (final field in fields) {
204-
result.writeln('_\$${_getGeneratedIdentifier(field)},');
205-
}
206-
result.writeln(']);');
207-
208-
if (generateMixin)
209-
result.write(_generateMixin(enumName, fields, valueOf, values));
210-
211-
return result.toString();
212-
}
213-
214-
String _generateMixin(String enumName, Iterable<FieldElement> fields,
215-
String valueOfIdentifier, String valuesIdentifier) {
216-
final result = new StringBuffer();
217-
218-
result.writeln('class _\$${enumName}Meta {');
219-
result.writeln('const _\$${enumName}Meta();');
220-
for (final field in fields) {
221-
final fieldName = field.displayName;
222-
result.writeln('$enumName get $fieldName => _\$${_getGeneratedIdentifier(
223-
field)};');
224-
}
225-
result.writeln(
226-
'$enumName valueOf(String name) => _\$$valueOfIdentifier(name);');
227-
result.writeln('BuiltSet<$enumName> get values => _\$$valuesIdentifier;');
228-
result.writeln('}');
229-
result.writeln('abstract class _\$${enumName}Mixin {');
230-
result.writeln(
231-
'_\$${enumName}Meta get $enumName => const _\$${enumName}Meta();');
232-
result.writeln('}');
233-
234-
return result.toString();
235-
}
236-
237-
String _getGeneratedIdentifier(FieldElement field) {
238-
final fieldName = field.displayName;
239-
return field.computeNode().toString().substring('$fieldName = _\$'.length);
240-
}
241-
242-
String _getValueOfIdentifier(ClassElement classElement, String enumName) {
243-
final getter = classElement.getMethod('valueOf');
244-
if (getter == null) return null;
245-
final source = getter.computeNode().toSource();
246-
final matches = new RegExp(r'static ' +
247-
enumName +
248-
r' valueOf\(String name\) \=\> \_\$(\w+)\(name\)\;')
249-
.allMatches(source);
250-
return matches.isEmpty ? null : matches.first.group(1);
251-
}
252-
253-
String _getValuesIdentifier(ClassElement classElement, String enumName) {
254-
final getter = classElement.getGetter('values');
255-
if (getter == null) return null;
256-
final source = getter.computeNode().toSource();
257-
final matches = new RegExp(
258-
r'static BuiltSet<' + enumName + r'> get values => _\$(\w+)\;')
259-
.allMatches(source);
260-
return matches.isEmpty ? null : matches.first.group(1);
261-
}
262-
263-
InvalidGenerationSourceError _makeError(Iterable<String> todos) {
264-
final message = new StringBuffer(
265-
'Please make the following changes to use EnumClass:\n');
266-
for (var i = 0; i != todos.length; ++i) {
267-
message.write('\n${i + 1}. ${todos.elementAt(i)}');
268-
}
21+
if (element is! LibraryElement) return null;
26922

270-
return new InvalidGenerationSourceError(message.toString());
23+
return new SourceLibrary.fromLibraryElement(element).generateCode();
27124
}
27225
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2016, Google Inc. Please see the AUTHORS file for details.
2+
// All rights reserved. Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/element/element.dart';
6+
import 'package:analyzer/dart/element/visitor.dart';
7+
import 'package:built_collection/built_collection.dart';
8+
9+
/// Tools for [LibraryElement]s.
10+
class LibraryElements {
11+
static BuiltList<ClassElement> getClassElements(
12+
LibraryElement libraryElement) {
13+
final result = new _GetClassesVisitor();
14+
libraryElement.visitChildren(result);
15+
return new BuiltList<ClassElement>(result.classElements);
16+
}
17+
}
18+
19+
/// Visitor that gets all [ClassElement]s.
20+
class _GetClassesVisitor extends SimpleElementVisitor {
21+
final List<ClassElement> classElements = new List<ClassElement>();
22+
23+
@override
24+
visitClassElement(ClassElement element) {
25+
classElements.add(element);
26+
}
27+
28+
@override
29+
visitCompilationUnitElement(CompilationUnitElement element) {
30+
element.visitChildren(this);
31+
}
32+
}

0 commit comments

Comments
 (0)