Skip to content

Commit

Permalink
Adapt abstract component fixme comment logic to match the mixin stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronlademann-wf committed Sep 16, 2019
1 parent aa6cef6 commit 94d1228
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import 'package:codemod/codemod.dart';
import 'package:over_react_codemod/src/react16_suggestors/react16_utilities.dart';

import '../constants.dart';
import 'component2_constants.dart';
import 'component2_utilities.dart';

/// Suggestor that replaces `UiComponent` with `UiComponent2` in extends clauses
Expand Down Expand Up @@ -87,7 +86,8 @@ class ClassNameAndAnnotationMigrator extends GeneralizingAstVisitor
visitClassDeclaration(ClassDeclaration node) {
super.visitClassDeclaration(node);

if (!shouldUpgradeAbstractComponents && canBeExtendedFrom(node)) {
if ((!allowPartialUpgrades && !fullyUpgradableToComponent2(node)) ||
(!shouldUpgradeAbstractComponents && canBeExtendedFrom(node))) {
return;
}

Expand All @@ -96,30 +96,6 @@ class ClassNameAndAnnotationMigrator extends GeneralizingAstVisitor
return;
}

if (!fullyUpgradableToComponent2(node)) {
if (!allowPartialUpgrades) return;

if (hasOneOrMoreMixins(node)) {
// Ensure that this comment patch is idempotent.
final classHasAlreadyBeenVisited = extendsName.toString().endsWith('2');
if (classHasAlreadyBeenVisited) return;

final indentationLevel = node.beginToken.charOffset;
final commentLineBeginning =
indentationLevel == 0 ? '///' : (' ' * indentationLevel) + '///';

if (node.documentationComment != null) {
yieldPatch(
node.documentationComment.end,
node.documentationComment.end,
'\n$commentLineBeginning\n$commentLineBeginning FIXME: Before upgrading this component to `${extendsName}2`, verify that none of the mixin(s) contain implementations of any React lifecycle methods that are not supported in `${extendsName}2`.');
} else {
yieldPatch(node.beginToken.offset, node.beginToken.offset,
'$commentLineBeginning FIXME: Before upgrading this component to `${extendsName}2`, verify that none of the mixin(s) contain implementations of any React lifecycle methods that are not supported in `${extendsName}2`.\n');
}
}
}

String reactImportName =
getImportNamespace(node, 'package:react/react.dart');
bool wasUpdated = false;
Expand Down Expand Up @@ -162,10 +138,7 @@ class ClassNameAndAnnotationMigrator extends GeneralizingAstVisitor
wasUpdated = true;
});

if (extendsName.name == 'UiComponent' ||
extendsName.name == 'UiStatefulComponent' ||
extendsName.name == 'FluxUiComponent' ||
extendsName.name == 'FluxUiStatefulComponent') {
if (upgradableV1ComponentClassNames.contains(extendsName.name)) {
// Update `UiComponent` or `UiStatefulComponent` extends clause.
yieldPatch(
extendsName.end,
Expand All @@ -177,15 +150,55 @@ class ClassNameAndAnnotationMigrator extends GeneralizingAstVisitor
}
}

// Add comment for abstract components that are updated
if (wasUpdated &&
canBeExtendedFrom(node) &&
!hasComment(node, sourceFile, abstractClassMessage)) {
yieldPatch(
node.offset,
node.offset,
'$abstractClassMessage\n',
);
if (wasUpdated) {
_addFixMeCommentPatches(node, extendsName);
}
}

void _addFixMeCommentPatches(ClassDeclaration node, Identifier extendsName) {
final extendsNameStr = extendsName.toString().replaceAll('2', '');
final classToUpgradeTo =
upgradableV1ComponentClassNames.contains(extendsNameStr)
? '`${extendsNameStr}2`'
: 'a class that now extends from `UiComponent2`';

[
_FixMeCommentPatch(hasOneOrMoreMixins, () => node,
'FIXME: Before upgrading this component to $classToUpgradeTo, verify that none of the mixin(s) contain implementations of any React lifecycle methods that are not supported in $classToUpgradeTo.'),
_FixMeCommentPatch(
(node) => shouldUpgradeAbstractComponents && canBeExtendedFrom(node),
() => node,
'FIXME: Abstract class has been updated to $classToUpgradeTo. This is a breaking change if this class is exported.'),
].forEach((patch) {
if (!patch.shouldYieldPatch(node) ||
hasMultilineDocComment(node, sourceFile, patch.commentSrc)) {
return;
}

final patchOffset = node.metadata?.beginToken?.offset ??
node.firstTokenAfterCommentAndMetadata.offset;
yieldPatch(patchOffset, patchOffset, patch.commentSrc);
});
}
}

class _FixMeCommentPatch {
final bool Function(ClassDeclaration node) shouldYieldPatch;
final ClassDeclaration Function() getNode;

_FixMeCommentPatch(this.shouldYieldPatch, this.getNode, String commentSrc) {
final node = getNode();
final indentationLevel = node.beginToken.charOffset;
final fixmeCommentLineStart =
indentationLevel == 0 ? '///' : (' ' * indentationLevel) + '///';
final fixmeCommentStart = node.documentationComment != null
? '$fixmeCommentLineStart\n$fixmeCommentLineStart'
: fixmeCommentLineStart;
final fixmeCommentEnd = '\n';

_commentSrc = '$fixmeCommentStart $commentSrc$fixmeCommentEnd';
}

String _commentSrc;
String get commentSrc => _commentSrc;
}
3 changes: 0 additions & 3 deletions lib/src/component2_suggestors/component2_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,3 @@ String getDeperecationMessage(String methodName) {
///
$revertInstructions''';
}

const abstractClassMessage =
'/// FIXME: Abstract class has been updated to Component2. This is a breaking change if this class is exported.';
8 changes: 8 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ const List<String> overReactMixinAnnotationNames = [
'StateMixin',
];

/// A list of the names of the core component classes that can be upgraded to a "v2" version.
const List<String> upgradableV1ComponentClassNames = [
'UiComponent',
'UiStatefulComponent',
'FluxUiComponent',
'FluxUiStatefulComponent',
];

/// Dart type for the static meta field on props classes.
const String propsMetaType = 'PropsMeta';

Expand Down
42 changes: 41 additions & 1 deletion lib/src/react16_suggestors/react16_utilities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,53 @@ bool hasComment(AstNode node, SourceFile sourceFile, String comment) {
return commentText?.contains(comment) ?? false;
}

/// Whether the [node] has a documentation comment that has
/// any lines that match lines found within the provided [comment].
bool hasMultilineDocComment(
AnnotatedNode node, SourceFile sourceFile, String comment) {
final nodeComments = nodeCommentSpan(node, sourceFile)
.text
.replaceAll('///', '')
.split('\n')
.map((line) => line.replaceAll('\n', '').trim())
.toList()
..removeWhere((line) => line.isEmpty);
final commentLines = comment
.replaceAll('///', '')
.trimLeft()
.split('\n')
.map((line) => line.replaceAll('\n', '').trim())
.toList()
..removeWhere((line) => line.isEmpty);

bool match = false;

for (var i = 0; i < commentLines.length; i++) {
final potentialMatch = commentLines[i];
if (nodeComments.any((line) => line == potentialMatch)) {
match = true;
break;
}
}

return match;
}

/// Returns the `SourceSpan` value of any comments on the provided [node] within the [sourceFile].
SourceSpan nodeCommentSpan(AnnotatedNode node, SourceFile sourceFile) {
return sourceFile.span(
node.beginToken.offset,
node.metadata?.beginToken?.offset ??
node.firstTokenAfterCommentAndMetadata.offset);
}

/// Returns an iterable of all the comments from [beginToken] to the end of the
/// file.
///
/// Comments are part of the normal stream, and need to be accessed via
/// [Token.precedingComments], so it's difficult to iterate over them without
/// this method.
Iterable allComments(Token beginToken) sync* {
Iterable<Token> allComments(Token beginToken) sync* {
var currentToken = beginToken;
while (!currentToken.isEof) {
var currentComment = currentToken.precedingComments;
Expand Down
Loading

0 comments on commit 94d1228

Please sign in to comment.