brew install peripheryapp/periphery/periphery
mint install peripheryapp/periphery
scan
La commande scan est la fonction principale de Periphery. Pour commencer une configuration guidée, accédez simplement au répertoire de votre projet et exécutez :
periphery scan --setup
La configuration guidée ne fonctionne que pour les projets Xcode et SwiftPM. Pour utiliser Periphery avec des systèmes de build non Apple tels que Bazel, voir Build Systems.
Après avoir répondu à quelques questions, Periphery imprimera la commande d'analyse complète et l'exécutera.
La configuration guidée est uniquement destinée à des fins d'introduction. Une fois que vous êtes familiarisé avec la périphérie, vous pouvez essayer des options plus avancées, qui peuvent toutes être vues avec periphery help scan
.
Pour obtenir des résultats cohérents à partir de la périphérie, il est crucial de comprendre les implications des cibles de build que vous choisissez d'analyser. Par exemple, imaginez un projet composé de trois cibles : App, Lib et Tests. La cible App importe Lib et les cibles Tests importent à la fois App et Lib. Si vous deviez fournir les trois à l'option --targets
, Periphery sera en mesure d'analyser votre projet dans son ensemble. Cependant, si vous choisissez d'analyser uniquement App et Lib, mais pas les tests, Periphery peut signaler certaines instances de code inutilisé qui sont uniquement référencées par les tests. Par conséquent, lorsque vous soupçonnez que Periphery a fourni un résultat incorrect, il est important de considérer les cibles que vous avez choisi d'analyser.
Si votre projet consiste en un ou plusieurs frameworks autonomes qui ne contiennent pas également une sorte d'application consommant leurs interfaces, vous devrez alors dire à Periphery de supposer que toutes les déclarations publiques sont en fait utilisées en incluant le --retain-public
option.
Pour les projets mixtes Objective-C et Swift, il est fortement recommandé de lire les implications que cela peut avoir sur vos résultats.
Une fois que vous avez choisi les options appropriées pour votre projet, vous souhaiterez peut-être les conserver dans un fichier de configuration YAML. Le moyen le plus simple d'y parvenir est d'exécuter Periphery avec l'option --verbose
. Vers le début de la sortie, vous verrez la section [configuration:begin]
avec votre configuration formatée en YAML ci-dessous. Copiez et collez la configuration dans .periphery.yml
à la racine du dossier de votre projet. Vous pouvez maintenant simplement exécuter periphery scan
et la configuration YAML sera utilisée.
La périphérie construit d'abord votre projet. Pour les projets Xcode, les schémas fournis via l'option --schemes
sont construits à l'aide de xcodebuild
. Pour les projets Swift Package Manager, les cibles individuelles fournies via l'option --targets
sont construites à l'aide de swift build
. Le compilateur Swift utilise une technique appelée index-while-building pour remplir un magasin d'index contenant des informations sur la structure du code source de votre projet.
Une fois votre projet construit, Periphery effectue une phase d'indexation. Pour chaque fichier source membre des cibles fournies via l'option --targets
, Periphery obtient ses informations structurelles à partir du magasin d'index et crée sa propre représentation graphique interne de votre projet. La périphérie analyse également l'arbre de syntaxe abstraite (AST) de chaque fichier pour remplir certains détails non fournis par le magasin d'index.
Une fois l'indexation terminée, Periphery analyse le graphique pour identifier le code inutilisé. Cette phase comprend un certain nombre d'étapes qui modifient le graphique pour faciliter l'identification de scénarios spécifiques de code inutilisé. La dernière étape parcourt le graphique depuis ses racines pour identifier les déclarations qui ne sont plus référencées.
Le but de Periphery est de signaler les instances de déclarations inutilisées. Une déclaration est une class
, struct
, protocol
, function
, property
, constructor
, enum
, typealias
, associatedtype
, etc. Comme vous vous en doutez, Periphery est capable d'identifier des déclarations simples non référencées, par exemple une class
qui n'est plus utilisée nulle part dans votre base de code.
La périphérie peut également identifier des instances plus avancées de code inutilisé. La section suivante les explique en détail.
La périphérie peut identifier les paramètres de fonction inutilisés. Les instances de paramètres inutilisés peuvent également être identifiées dans les protocoles et leurs déclarations conformes, ainsi que les paramètres dans les méthodes remplacées. Ces deux scénarios sont expliqués plus en détail ci-dessous.
Un paramètre inutilisé d'une fonction de protocole ne sera signalé comme inutilisé que si le paramètre est également inutilisé dans toutes les implémentations.
protocol Greeter {
func greet ( name : String )
func farewell ( name : String ) // 'name' is unused
}
class InformalGreeter : Greeter {
func greet ( name : String ) {
print ( " Sup " + name + " . " )
}
func farewell ( name : String ) { // 'name' is unused
print ( " Cya. " )
}
}
Conseil
Vous pouvez ignorer tous les paramètres inutilisés des protocoles et des fonctions conformes avec l'option
--retain-unused-protocol-func-params
.
À l'instar des protocoles, les paramètres des fonctions remplacées ne sont signalés comme inutilisés que s'ils le sont également dans la fonction de base et dans toutes les fonctions prioritaires.
class BaseGreeter {
func greet ( name : String ) {
print ( " Hello. " )
}
func farewell ( name : String ) { // 'name' is unused
print ( " Goodbye. " )
}
}
class InformalGreeter : BaseGreeter {
override func greet ( name : String ) {
print ( " Sup " + name + " . " )
}
override func farewell ( name : String ) { // 'name' is unused
print ( " Cya. " )
}
}
Les paramètres inutilisés des protocoles ou des classes définis dans les modules étrangers (par exemple Foundation) sont toujours ignorés, puisque vous n'avez pas accès pour modifier la déclaration de la fonction de base.
Les paramètres inutilisés des fonctions qui appellent simplement fatalError
sont également ignorés. De telles fonctions sont souvent des initialiseurs requis non implémentés dans les sous-classes.
class Base {
let param : String
required init ( param : String ) {
self . param = param
}
}
class Subclass : Base {
init ( custom : String ) {
super . init ( param : custom )
}
required init ( param : String ) {
fatalError ( " init(param:) has not been implemented " )
}
}
Un protocole auquel un objet se conforme n'est pas véritablement utilisé à moins qu'il ne soit également utilisé comme type existentiel ou pour spécialiser une méthode/classe générique. La périphérie est capable d'identifier ces protocoles redondants, qu'ils soient conformes à un ou même à plusieurs objets.
protocol MyProtocol { // 'MyProtocol' is redundant
func someMethod ( )
}
class MyClass1 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass1! " )
}
}
class MyClass2 : MyProtocol { // 'MyProtocol' conformance is redundant
func someMethod ( ) {
print ( " Hello from MyClass2! " )
}
}
let myClass1 = MyClass1 ( )
myClass1 . someMethod ( )
let myClass2 = MyClass2 ( )
myClass2 . someMethod ( )
Ici, nous pouvons voir que même si les deux implémentations de someMethod
sont appelées, à aucun moment un objet ne prend le type de MyProtocol
. Par conséquent, le protocole lui-même est redondant et il n'y a aucun avantage à ce que MyClass1
ou MyClass2
s'y conforment. Nous pouvons supprimer MyProtocol
avec chaque conformité redondante et simplement conserver someMethod
dans chaque classe.
Tout comme une méthode ou une propriété normale d'un objet, les propriétés et méthodes individuelles déclarées par votre protocole peuvent également être identifiées comme inutilisées.
protocol MyProtocol {
var usedProperty : String { get }
var unusedProperty : String { get } // 'unusedProperty' is unused
}
class MyConformingClass : MyProtocol {
var usedProperty : String = " used "
var unusedProperty : String = " unused " // 'unusedProperty' is unused
}
class MyClass {
let conformingClass : MyProtocol
init ( ) {
conformingClass = MyConformingClass ( )
}
func perform ( ) {
print ( conformingClass . usedProperty )
}
}
let myClass = MyClass ( )
myClass . perform ( )
Ici, nous pouvons voir que MyProtocol
est lui-même utilisé et ne peut pas être supprimé. Cependant, comme unusedProperty
n'est jamais appelé sur MyConformingClass
, Periphery est capable d'identifier que la déclaration de unusedProperty
dans MyProtocol
est également inutilisée et peut être supprimée avec l'implémentation inutilisée de unusedProperty
.
En plus d'être capable d'identifier les énumérations inutilisées, Periphery peut également identifier les cas d'énumération individuels inutilisés. Les énumérations simples qui ne sont pas représentables brutes, c'est-à-dire qui n'ont pas de type de valeur String
, Character
, Int
ou à virgule flottante, peuvent être identifiées de manière fiable. Toutefois, les énumérations qui ont un type de valeur brute peuvent être de nature dynamique et doivent donc être supposées être utilisées.
Clarifions cela avec un exemple rapide :
enum MyEnum : String {
case myCase
}
func someFunction ( value : String ) {
if let myEnum = MyEnum ( rawValue : value ) {
somethingImportant ( myEnum )
}
}
Il n'y a pas de référence directe au cas myCase
, il est donc raisonnable de s'attendre à ce qu'il ne soit plus nécessaire, mais s'il était supprimé, nous pouvons voir que somethingImportant
ne serait jamais appelé si someFunction
recevait la valeur de "myCase"
.
Les propriétés attribuées mais jamais utilisées sont identifiées comme telles, par exemple :
class MyClass {
var assignOnlyProperty : String // 'assignOnlyProperty' is assigned, but never used
init ( value : String ) {
self . assignOnlyProperty = value
}
}
Dans certains cas, cela peut être le comportement souhaité. Vous disposez donc de quelques options pour masquer ces résultats :
--retain-assign-only-property-types
. Les types donnés doivent correspondre à leur utilisation exacte dans la déclaration de propriété (sans point d'interrogation facultatif), par exemple String
, [String]
, Set
. La périphérie n'est pas en mesure de résoudre les types de propriétés déduits. Par conséquent, dans certains cas, vous devrez peut-être ajouter des annotations de type explicites à vos propriétés.--retain-assign-only-properties
. Les déclarations marquées comme public
mais qui ne sont pas référencées depuis l'extérieur de leur module d'accueil sont identifiées comme ayant une accessibilité publique redondante. Dans ce scénario, l'annotation public
peut être supprimée de la déclaration. La suppression de l'accessibilité publique redondante présente plusieurs avantages :
final
en découvrant automatiquement toutes les déclarations potentiellement prioritaires. les classes final
sont mieux optimisées par le compilateur. Cette analyse peut être désactivée avec --disable-redundant-public-analysis
.
La périphérie peut détecter les importations inutilisées de cibles qu'elle a analysées, c'est-à-dire celles spécifiées avec l'argument --targets
. Il ne peut pas détecter les importations inutilisées d'autres cibles car les fichiers sources Swift ne sont pas disponibles et les utilisations de @_exported
ne peuvent pas être observées. @_exported
est problématique car il modifie l'interface publique d'une cible de telle sorte que les déclarations exportées par la cible ne sont plus nécessairement déclarées par la cible importée. Par exemple, la cible Foundation
exporte Dispatch
, entre autres cibles. Si un fichier source donné importe Foundation
et fait référence DispatchQueue
mais à aucune autre déclaration de Foundation
, alors l'importation Foundation
ne peut pas être supprimée car cela rendrait également le type DispatchQueue
indisponible. Par conséquent, pour éviter les faux positifs, Periphery détecte uniquement les importations inutilisées des cibles qu’il a analysées.
La périphérie produira probablement des faux positifs pour les cibles avec un mélange de Swift et d'Objective-C, car la périphérie ne peut pas analyser les fichiers Objective-C. Il est donc recommandé de désactiver la détection des importations inutilisées pour les projets comportant une quantité importante d'Objective-C, ou d'exclure manuellement les cibles de langages mixtes des résultats.
La périphérie ne peut pas analyser le code Objective-C car les types peuvent être typés dynamiquement.
Par défaut, Periphery ne suppose pas que les déclarations accessibles par le runtime Objective-C sont utilisées. Si votre projet est un mélange de Swift et Objective-C, vous pouvez activer ce comportement avec l'option --retain-objc-accessible
. Les déclarations Swift accessibles par le runtime Objective-C sont celles qui sont explicitement annotées avec @objc
ou @objcMembers
, et les classes qui héritent NSObject
directement ou indirectement via une autre classe.
Alternativement, --retain-objc-annotated
peut être utilisé pour conserver uniquement les déclarations explicitement annotées avec @objc
ou @objcMembers
. Les types qui héritent NSObject
ne sont pas conservés sauf s'ils comportent des annotations explicites. Cette option peut révéler davantage de code inutilisé, mais avec l'avertissement que certains résultats peuvent être incorrects si la déclaration est en fait utilisée dans du code Objective-C. Pour résoudre ces résultats incorrects, vous devez ajouter une annotation @objc
à la déclaration.
Swift synthétise du code supplémentaire pour les types Codable
qui n'est pas visible par la périphérie et peut entraîner des faux positifs pour des propriétés non directement référencées à partir du code non synthétisé. Si votre projet contient de nombreux types de ce type, vous pouvez conserver toutes les propriétés des types Codable
avec --retain-codable-properties
. Alternativement, vous pouvez conserver les propriétés uniquement sur les types Encodable
avec --retain-encodable-properties
.
Si la conformité Codable
est déclarée par un protocole dans un module externe non analysé par Periphery, vous pouvez demander à Periphery d'identifier les protocoles comme Codable
avec --external-codable-protocols "ExternalProtocol"
.
Toute classe qui hérite XCTestCase
est automatiquement conservée avec ses méthodes de test. Cependant, lorsqu'une classe hérite indirectement XCTestCase
via une autre classe, par exemple UnitTestCase
, et que cette classe réside dans une cible qui n'est pas analysée par Periphery, vous devez utiliser l'option --external-test-case-classes UnitTestCase
pour demander à Periphery de traiter UnitTestCase
comme une sous-classe XCTestCase
.
Si votre projet contient des fichiers Interface Builder (tels que des storyboards et des XIB), Periphery en tiendra compte lors de l'identification des déclarations inutilisées. Cependant, Periphery identifie actuellement uniquement les classes inutilisées. Cette limitation existe car Periphery n'analyse pas encore complètement les fichiers Interface Builder (voir le numéro 212). En raison du principe de conception de Periphery consistant à éviter les faux positifs, il est supposé que si une classe est référencée dans un fichier Interface Builder, tous ses IBOutlets
et IBActions
sont utilisés, même s'ils ne le sont pas dans la réalité. Cette approche sera révisée pour identifier avec précision IBActions
et IBOutlets
inutilisés une fois que Periphery aura acquis la capacité d'analyser les fichiers Interface Builder.
Pour une raison quelconque, vous souhaiterez peut-être conserver du code inutilisé. Les commandes de commentaires du code source peuvent être utilisées pour ignorer des déclarations spécifiques et les exclure des résultats.
Une commande ignore comment peut être placée directement sur la ligne au-dessus de toute déclaration pour l'ignorer, ainsi que toutes les déclarations descendantes :
// periphery:ignore
class MyClass { }
Vous pouvez également ignorer les paramètres de fonction spécifiques inutilisés :
// periphery:ignore:parameters unusedOne,unusedTwo
func someFunc ( used : String , unusedOne : String , unusedTwo : String ) {
print ( used )
}
La // periphery:ignore:all
peut être placée en haut du fichier source pour ignorer tout le contenu du fichier. Notez que le commentaire doit être placé au-dessus de tout code, y compris les instructions d'importation.
Les commandes de commentaires prennent également en charge les commentaires de fin suivis d'un trait d'union afin que vous puissiez inclure une explication sur la même ligne :
// periphery:ignore - explanation of why this is necessary
class MyClass { }
Avant de configurer l'intégration Xcode, nous vous recommandons fortement de faire fonctionner Periphery dans un terminal, car vous utiliserez exactement la même commande via Xcode.
Sélectionnez votre projet dans le navigateur de projet et cliquez sur le bouton + en bas à gauche de la section Cibles. Sélectionnez Multiplateforme et choisissez Agrégat . Cliquez sur Suivant.
La périphérie est un projet passionnant qui demande énormément d’efforts à maintenir et à développer. Si vous trouvez Periphery utile, veuillez envisager de parrainer via les sponsors GitHub.
Des remerciements particuliers vont aux généreux sponsors suivants :
SaGa Corp développe une technologie unique pour les acteurs financiers et leurs clients.
Emerge Tools est une suite de produits révolutionnaires conçus pour dynamiser les applications mobiles et les équipes qui les créent.