Skip to content

Commit

Permalink
automatically apply FixIts
Browse files Browse the repository at this point in the history
  • Loading branch information
MahdiBM committed Jul 23, 2024
1 parent f1e8be0 commit f6b4438
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 208 deletions.
6 changes: 3 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "471e59eb3ce1f697200ce8e807d75c97a2b2065d8033fd5c430fc887e3c33f05",
"originHash" : "6014e531cd0700b62246ca0fa1188acb8e5c483aab4374ff903c74b6c9cfa34b",
"pins" : [
{
"identity" : "swift-mustache",
Expand All @@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
"version" : "510.0.2"
"revision" : "4c6cc0a3b9e8f14b3ae2307c5ccae4de6167ac2c",
"version" : "600.0.0-prerelease-2024-06-12"
}
}
],
Expand Down
4 changes: 3 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/swiftlang/swift-syntax",
"510.0.0" ..< "610.0.0"
from: "510.0.0"
),
.package(
url: "https://github.com/hummingbird-project/swift-mustache",
Expand All @@ -34,6 +34,7 @@ let package = Package(
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "Mustache", package: "swift-mustache"),
],
swiftSettings: swiftSettings
Expand All @@ -52,6 +53,7 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "Mustache", package: "swift-mustache"),
],
swiftSettings: swiftSettings
Expand Down
4 changes: 3 additions & 1 deletion Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/swiftlang/swift-syntax",
"510.0.0" ..< "610.0.0"
.upToNextMinor(from: "600.0.0-prerelease-2024-06-12")
),
.package(
url: "https://github.com/hummingbird-project/swift-mustache",
Expand All @@ -34,6 +34,7 @@ let package = Package(
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "Mustache", package: "swift-mustache"),
],
swiftSettings: swiftSettings
Expand All @@ -52,6 +53,7 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "Mustache", package: "swift-mustache"),
],
swiftSettings: swiftSettings
Expand Down
138 changes: 0 additions & 138 deletions Sources/EnumeratorMacro/Enumerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,141 +6,3 @@ public macro Enumerator(
module: "EnumeratorMacroImpl",
type: "EnumeratorMacroType"
)

@Enumerator("""
var caseName: String {
switch self {
{{#cases}}
case .{{name}}: "{{#first(parameters)}} {{name}} {{/first(parameters)}}"
{{/cases}}
}
}
""")
enum TestEnum {
case a(val1: String, val2: Int)
case b
case testCase(testValue: String)
}

public protocol LocalizationServiceProtocol {
static func localizedString(language: String, term: String, parameters: Any...) -> String
}

package enum SharedConfiguration {
package enum Env: String {
case local
case testing
case prod
}

package static var env: Env { fatalError() }
}

@Enumerator(allowedComments: ["business_error", "l8n_params"],
"""
public enum Subtype: String, Equatable {
{{#cases}}
case {{name}}
{{/cases}}
}
""",
"""
public var subtype: Subtype {
switch self {
{{#cases}}
case .{{name}}:
.{{name}}
{{/cases}}
}
}
""",
"""
public var errorCode: String {
switch self {
{{#cases}}
case .{{name}}:
"ERROR-{{plusOne(index)}}"
{{/cases}}
}
}
""",
"""
public var loggerMetadata: [String: String] {
switch self {
{{#cases}} {{^isEmpty(parameters)}}
case let .{{name}}{{withParens(joined(names(parameters)))}}:
[
"caseName": self.caseName,
{{#names(parameters)}}
"case_{{.}}": String(reflecting: {{.}}),
{{/names(parameters)}}
]
{{/isEmpty(parameters)}} {{/cases}}
default:
["caseName": self.caseName]
}
}
""",
"""
private var localizationParameters: [Any] {
switch self {
{{#cases}} {{^isEmpty(parameters)}}
{{^isEmpty(l8n_params(comments))}}
case let .{{name}}{{withParens(joined(names(parameters)))}}:
[{{l8n_params(comments)}}]
{{/isEmpty(l8n_params(comments))}}
{{^exists(l8n_params(comments))}}
case let .{{name}}{{withParens(joined(names(parameters)))}}:
[
{{#parameters}}
{{name}}{{#isOptional}} as Any{{/isOptional}},
{{/parameters}}
]
{{/exists(l8n_params(comments))}}
{{/isEmpty(parameters)}} {{/cases}}
default:
[]
}
}
""")
@Enumerator(
allowedComments: ["business_error", "l8n_params"],
#"""
package var isBusinessLogicError: Bool {
switch self {
case
{{#cases}}{{#bool(business_error(comments))}}
.{{name}},
{{/bool(business_error(comments))}}{{/cases}}
:
return true
default:
return false
}
}
"""#
)
public enum ErrorMessage {
public static let localizationServiceType: LocalizationServiceProtocol.Type? = nil

case allergenAlreadyAdded // business_error
case alreadyOngoingInventory
case apiKeyWithoutEnoughPermission(integration: String, other: Bool?, Int)
case databaseError(error: Error, isConstraintViolation: Bool) // business_error; l8n_params:

public var caseName: String {
self.subtype.rawValue
}

public func toString(_ language: String) -> String {
let translation = Self.localizationServiceType?.localizedString(
language: language,
term: "api.\(self.caseName)",
parameters: self.localizationParameters
)
return translation ?? (SharedConfiguration.env == .testing ? String(reflecting: self) : "<localization failed to load for \(self.caseName)>")
}
}
107 changes: 70 additions & 37 deletions Sources/EnumeratorMacroImpl/EnumeratorMacroType.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@_spi(FixItApplier) import SwiftIDEUtils
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacros
Expand Down Expand Up @@ -113,51 +114,83 @@ extension EnumeratorMacroType: MemberMacro {
from: &parser
).statements.compactMap { statement -> DeclSyntax? in
var statement = statement

var diagnostics = ParseDiagnosticsGenerator.diagnostics(for: statement)
if diagnostics.containsError {
/// Try to recover from errors:

/// Returns if anything changed at all.
func tryApplyFixIts() -> Bool {
guard diagnostics.contains(where: { !$0.fixIts.isEmpty }) else {
return false
}
let fixedStatement = FixItApplier.applyFixes(
from: diagnostics,
filterByMessages: nil,
to: statement
)
var parser = Parser(fixedStatement)
let newStatement = CodeBlockItemSyntax.parse(from: &parser)
guard statement != newStatement else {
return false
}
let placeholderDetector = PlaceholderDetector()
placeholderDetector.walk(newStatement)
/// One of the FixIts added a placeholder, so the fixes are unacceptable
/// Known behavior which is fine for now: even if one FixIt is
/// misbehaving, still none of the FixIts will be applied.
if placeholderDetector.containedPlaceholder {
return false
} else {
statement = newStatement
return true
}
}

/// Returns if anything changed at all.
func tryManuallyFixErrors() -> Bool {
let switchRewriter = SwitchErrorsRewriter()
let fixedStatement = switchRewriter.rewrite(statement)
let newDiagnostics = ParseDiagnosticsGenerator.diagnostics(for: fixedStatement)
if !newDiagnostics.containsError {
switch CodeBlockItemSyntax(fixedStatement) {
case let .some(fixedStatement):
statement = fixedStatement
diagnostics = newDiagnostics
case .none:
context.diagnose(
Diagnostic(
node: codeSyntax,
message: MacroError.internalError(
"Could not convert a Syntax to a CodeBlockItemSyntax"
)
switch CodeBlockItemSyntax(fixedStatement) {
case let .some(fixedStatement):
statement = fixedStatement
return true
case .none:
context.diagnose(
Diagnostic(
node: codeSyntax,
message: MacroError.internalError(
"Could not convert a Syntax to a CodeBlockItemSyntax"
)
)
return nil
}
} else {
/// If not recovered, throw a diagnostic error.
context.diagnose(.init(
node: codeSyntax,
message: MacroError.renderedSyntaxContainsErrors(statement.description)
))
)
return false
}
}

for diagnostic in diagnostics {
if diagnostic.diagMessage.severity == .error {
context.diagnose(.init(
node: codeSyntax,
position: diagnostic.position,
message: diagnostic.diagMessage,
highlights: diagnostic.highlights,
notes: diagnostic.notes,
fixIts: diagnostic.fixIts
))
} else if let /*fixIt*/_ = diagnostic.fixIts.first {
/// TODO: Apply the fixit
}
if tryApplyFixIts() {
diagnostics = ParseDiagnosticsGenerator.diagnostics(for: statement)
}

if diagnostics.containsError, tryManuallyFixErrors() {
diagnostics = ParseDiagnosticsGenerator.diagnostics(for: statement)
}

if diagnostics.containsError {
/// If still not recovered, throw a diagnostic error.
context.diagnose(.init(
node: codeSyntax,
message: MacroError.renderedSyntaxContainsErrors(statement.description)
))
}

for diagnostic in diagnostics
where diagnostic.diagMessage.severity == .error {
context.diagnose(.init(
node: codeSyntax,
position: diagnostic.position,
message: diagnostic.diagMessage,
highlights: diagnostic.highlights,
notes: diagnostic.notes,
fixIts: diagnostic.fixIts
))
}
if diagnostics.containsError {
return nil
Expand Down
17 changes: 17 additions & 0 deletions Sources/EnumeratorMacroImpl/Visitors/PlacehodlerDetector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import SwiftSyntax

final class PlaceholderDetector: SyntaxVisitor {
var containedPlaceholder = false

override init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
super.init(viewMode: viewMode)
}

override func visit(_ node: TokenSyntax) -> SyntaxVisitorContinueKind {
if node.isEditorPlaceholder {
self.containedPlaceholder = true
return .skipChildren
}
return .visitChildren
}
}
Loading

0 comments on commit f6b4438

Please sign in to comment.