SwiftLint - отличный инструмент с открытым исходным кодом, который упрощает соблюдение стиля и соглашений Swift. Это также помогает выявлять возможные ошибки на раннем этапе, выделяя проблемное использование. Вы можете запустить SwiftLint в своем проекте Xcode, чтобы увидеть все исключения руководства по стилю в строках, где они возникают, и быстро исправить их. Мне очень помогло, когда Я перенес свой код с Objective-c на Swift.

Чтобы сделать SwiftLint еще более полезным для разработчиков Firebase, мы добавили в SwiftLint несколько новых экспериментальных правил Firebase. Эти правила будут отображать предупреждения об общих ошибках, которые могут привести к ошибкам при использовании Firebase SDK.

Не имея никаких знаний о разработке линтеров или правил, я погрузился в правила SwiftLint. SwiftLint разработан для обеспечения соблюдения соглашений о стилях. Таким образом, большинство его правил проверяют наличие определенных ключевых слов или символов. И где они есть. Но в правилах Firebase у меня была другая цель. Правильные вызовы методов должны находиться в правильных файлах в рамках правильных функций. Например правила FirebaseCore. Мы хотим убедиться, что вызывается FirebaseApp.configure (). И он вызывается внутри функции application: didFinishLaunchingWithOptions: UIApplicationDelegate.

Моя первоначальная попытка заключалась в выполнении сложной проверки регулярного выражения. Чтобы найти конкретную функцию класса, вызовите метод Firebase и посмотрите, находятся ли они на одной границе. Это оказалось довольно неэффективным и остановило Xcode.

Я просмотрел вспомогательную функцию SwiftLint и обнаружил, что уже существует функция, выполняющая это за меня.

func match(pattern: String,
           range: NSRange? = nil,
           excludingSyntaxKinds: [SyntaxKind],
           excludingPattern: String,             
           exclusionMapping: MatchMapping = { $0.range })->[NSRange]

Это обнаруживает, возникает ли шаблон, а другой - нет. Так я наконец начал видеть результаты и заставлял правила работать.

Я воспринял это как первую попытку запроса на вытягивание. Но я обнаружил, что это приведет к слишком большому количеству ложных срабатываний. Поскольку я выполнял базовую проверку регулярного выражения вместо реальной проверки синтаксиса. Это заставило меня глубоко погрузиться в SwiftLint, чтобы понять, как он на самом деле выполняет проверку синтаксиса.

Под SwiftLint есть очаровательный маленький фреймворк под названием SourceKitten. Он взаимодействует с SourceKit для анализа Swift AST. Когда у вас есть AST, вы можете выполнить правильную проверку синтаксиса.

{
  "key.substructure" : [
    {
      "key.kind" : "source.lang.swift.decl.struct",
      "key.offset" : 0,
      "key.nameoffset" : 7,
      "key.namelength" : 1,
      "key.bodyoffset" : 10,
      "key.bodylength" : 13,
      "key.length" : 24,
      "key.substructure" : [
        {
          "key.kind" : "source.lang.swift.decl.function.method.instance",
          "key.offset" : 11,
          "key.nameoffset" : 16,
          "key.namelength" : 3,
          "key.bodyoffset" : 21,
          "key.bodylength" : 0,
          "key.length" : 11,
          "key.substructure" : [

          ],
          "key.name" : "b()"
        }
      ],
      "key.name" : "A"
    }
  ],
  "key.offset" : 0,
  "key.diagnostic_stage" : "source.diagnostic.stage.swift.parse",
  "key.length" : 24
}

Таким образом, я вернулся к быстрому запуску Firebase, распечатал их Swift AST из интерфейса командной строки этого инструмента. Я использовал SwiftExpressionKind и SwiftDeclarationKind от SourceKitten. Затем повторил через AST. Таким образом, у меня наконец были твердые правила. Они проверяли точные функции и типы синтаксиса в конкретных файлах. Я понял, что доступных вспомогательных рекурсивных функций для моего случая недостаточно. Поэтому я расширил их для себя, чтобы достичь этой цели.

public func validateRecursive(dictionary: [String:  
    SourceKitRepresentable]) -> Bool {
  if validateBaseCase(dictionary: dictionary) {
    return true
  }
  for subDict in dictionary.substructure {
    if validateRecursive(dictionary: subDict) {                   
      return true
    }
  }
  return false
}

После завершения юнит-тестов я вернулся с еще одним пиаром. На этот раз недостающей частью были примеры. Каждое правило SwiftLint поставляется с примерами срабатывания и без срабатывания. Это помогает сделать ваш код более читабельным. Но примеры также используются в модульных тестах. Приведя больше примеров, я проверил и ложные срабатывания, и ложноотрицательные.

public static let description = RuleDescription(
  identifier: "firebase_config_activate",
  name: "Firebase Config Activate",
  description: "Firebase Config should be activated.",          
  
  nonTriggeringExamples: [              
    "remoteConfig.fetch(withExpirationDuration:    
        TimeInterval(expirationDuration)) {" +                 
        " (status, error) -> Void in \n 
            self.remoteConfig.activateFetched() \n }",            
    "foo.fetch() { }",
    "foo.fetch(fromURL: URL) { }"
  ],
  
  triggeringExamples: [              
    "remoteConfig.fetch(withExpirationDuration: 
        TimeInterval(expirationDuration)) {" +                
        " (status, error) -> Void in \n }",            
    "remoteConfig.fetch(withExpirationDuration: 
        TimeInterval(expirationDuration)) {" +
        " (status, error) -> Void in \n foo.fetch() \n }"
  ]
)

В настоящее время мы размещаем правила на нашей вилке в ветке firebase_rules. Наш предварительный двоичный файл содержит правила Firebase. Вы можете просто загрузить файл .pkg и дважды щелкнуть его для установки. Вы также можете собрать двоичный файл из исходного кода.

Поскольку правила являются обязательными, вам необходимо добавить файл .swiftlint.yml в ту же папку, что и исходные файлы Swift, содержащий следующий текст:

opt_in_rules:
  - firebase_config_activate
  - firebase_config_defaults
  - firebase_config_fetch
  - firebase_core
  - firebase_dynamiclinks_customschemeURL
  - firebase_dynamiclinks_schemeURL
  - firebase_dynamiclinks_universallink
  - firebase_invites

Затем просто запустите SwiftLint в своем проекте, как обычно.

Мы будем рады, если вы попробуете и отправите нам отзыв в Twitter с #FirebaseLinter. Вы также можете задавать вопросы о StackOverflow, используя теги firebase и swiftlint вместе.