Documentor est un petit outil Pharo qui produit un rapport sur une classe sous forme de document Microdown.
Le rapport est généré avec MicMicrodownTextualBuilder. L'outil ne concatène jamais de chaînes de caractères pour mettre en forme sa sortie : chaque titre, paragraphe, élément de liste et mise en valeur passe par le builder.
Le projet est construit en TDD (tests d'abord), par versions :
| Version | Contenu | Statut |
|---|---|---|
| 1 | Nom de la classe, superclasse, champs, package | Terminée |
| 2 | Premier paragraphe du commentaire de classe, position dans la hiérarchie, méthodes avec leur premier commentaire | Terminée |
| 3 | Métriques (profondeur d'héritage, nombre de champs et de méthodes, appels à super, méthodes longues) |
Terminée |
| 4 | Mini linter basé sur un visiteur | Pas commencée |
- Une image Pharo récente. Microdown est fourni avec, il n'y a rien à installer.
- Le code utilise les classes d'AST
OC*(OCVariableNode, ...) etMicrodownParser. Si votre image utilise d'autres noms, adaptez les méthodes concernées (voir Limites connues).
- Clonez le dépôt avec Iceberg (ou ajoutez-le depuis le navigateur Iceberg).
- Vérifiez que les packages
DocumentoretDocumentor-Testssont chargés dans l'image. - Lancez les tests (voir Lancer les tests).
| Classe | Rôle |
|---|---|
Documentor |
Point d'entrée. Contient la classe cible et un unique MicMicrodownTextualBuilder. Chaque méthode report... écrit une section dans ce builder. |
ClassMetrics |
Calcule les chiffres de la version 3. Elle renvoie des nombres et des collections simples et ne connaît rien de Microdown, ce qui la rend facile à tester. |
DocMethod |
Petite classe utilitaire qui extrait le premier commentaire d'une méthode compilée. |
La règle de conception est calculer d'un côté, présenter de l'autre : ClassMetrics et DocMethod produisent des données, et Documentor est la seule classe qui les transforme en appels au builder.
| Classe | Rôle |
|---|---|
DocFakeAnimal |
Fixture. Champs name et age. Un commentaire de classe en deux paragraphes. Méthodes age (sans commentaire), describe et name (avec commentaire). |
DocFakeDog |
Fixture, sous-classe de DocFakeAnimal. Champ breed. Méthodes bark, describe (redéfinit et envoie super) et longMethod (une méthode volontairement longue). |
DocumentorTest |
Tests du contenu du rapport (versions 1 et 2, plus la présence de la section métriques). |
ClassMetricsTest |
Tests des métriques de la version 3. |
Les tests s'exécutent sur des classes fixtures que vous contrôlez, plutôt que sur des classes comme OrderedCollection qui peuvent changer d'une version de Pharo à l'autre.
(Documentor on: DocFakeDog) report.report renvoie une chaîne Microdown. Inspectez-la (Cmd/Ctrl+I) ou affichez-la (Cmd/Ctrl+P). Un nouveau rapport est produit à chaque appel, donc appeler report deux fois donne le même résultat.
(Documentor on: OrderedCollection) report.| report |
report := (Documentor on: DocFakeDog) report.
'/tmp/DocFakeDog.md' asFileReference writeStreamDo: [ :stream |
stream nextPutAll: report ].metrics := ClassMetrics on: DocFakeDog.
metrics depthOfInheritance. "3"
metrics inheritedFieldsCount. "2"
metrics definedFieldsCount. "1"
metrics definedMethodsCount. "3"
metrics superSendingMethods. "la méthode compilée DocFakeDog>>#describe"
metrics longMethodThreshold: 5.
metrics longMethods. "la méthode compilée DocFakeDog>>#longMethod"Pour (Documentor on: DocFakeDog) report, le résultat ressemble à ceci. Les espaces exacts dépendent de votre version de Microdown, et les ... remplacent des parties qui dépendent de votre image.
# DocFakeDog
A subclass of DocFakeAnimal
has the following fields: #breed
in defined in package `Documentor-Tests`
With a first comment paragraph: ...
## Class hierarchy
- ProtoObject
- Object
- DocFakeAnimal
- **DocFakeDog**
## Has methods:
- `bark` - Makes the dog bark.
- `describe` - Returns a description of the dog.
- `longMethod`
## Metrics
- Depth of inheritance: 3
- Inherited fields: 2
- Defined fields: 1
- Inherited methods: ...
- Defined methods: 3
- Methods invoking super methods: ... DocFakeDog>>#describe ...
- Long methods: ...Le texte du rapport reste en anglais, comme dans le code.
Côté classe
on: aClasscrée unDocumentorpouraClass. Il utilisetargetClass:et renvoie la nouvelle instance.
Point d'entrée
reportcrée un nouveau builder, appelle les méthodes de section dans l'ordre, puis renvoiebuilder contents. L'ordre est : nom de la classe, héritage, champs, package, commentaire, hiérarchie, méthodes, métriques. Créer le builder ici, et n'appelercontentsqu'une seule fois à la fin, permet aux sections de partager un seul document et rendreportrépétable.
Méthodes de section (elles écrivent dans le builder partagé et ne renvoient rien d'utile)
reportClassNameécrit un titre de niveau 1 avec le nom de la classe.reportInheritanceécrit un paragraphe « A subclass of » suivi du nom de la superclasse.reportFieldsécrit un paragraphe « has the following fields: » suivi d'un#nompar champ. Elle utiliseinstVarNames, donc seuls les champs définis dans la classe elle-même sont listés (les champs hérités sont comptés dans les métriques).reportPackageécrit un paragraphe avec le nom du package en monospace.reportCommentécrit un paragraphe avec le premier paragraphe du commentaire de classe, obtenu avecfirstParagraphOfComment.reportHierarchyécrit un titre de niveau 2, puis construit la chaîneallSuperclasses reversed , { targetClass }(de la racine jusqu'à la cible) et la passe àreportChain:.reportChain:est récursive. Chaque appel écrit un élément de liste contenant la première classe de la chaîne, le reste de la chaîne étant dans une liste imbriquée. Quand il ne reste qu'une classe (la cible), elle est écrite en gras et suivie de ses sous-classes directes viareportSubClassesOf:.reportSubClassesOf:liste les sous-classes directes d'une classe dans une liste imbriquée, triées par nom. Elle n'écrit rien s'il n'y en a pas. Seules les sous-classes directes sont affichées, pour qu'une classe proche du sommet de la hiérarchie ne submerge pas le rapport.reportMethodsliste les méthodes définies dans la classe (localMethods), triées par sélecteur. Chaque élément montre le sélecteur en monospace et, si la méthode en a un, son premier commentaire, obtenu avecDocMethod>>firstCommentOf:. Une méthode sans commentaire est quand même listée.reportMetricscrée unClassMetricssur la classe cible et écrit un élément de liste par métrique.
Méthode utilitaire
firstParagraphOfCommentanalyse le commentaire de classe avecMicrodownParser, prend le premierMicParagraphBlockparmi les enfants de la racine et renvoie son texte. Elle renvoie une chaîne vide si le commentaire n'a pas de paragraphe. Un paragraphe se termine à une ligne vide : un simple retour à la ligne ne le termine pas, et une ligne « vide » qui contient un espace peut ne pas le terminer non plus.
Accesseur
targetClass:enregistre la classe à documenter.
Définitions utilisées par les métriques (ce sont des choix, et les tests les fixent) :
| Méthode | Signification |
|---|---|
depthOfInheritance |
Nombre de superclasses au-dessus de la classe (allSuperclasses size). |
inheritedFieldsCount |
Nombre de champs déclarés au-dessus de la classe, 0 sans superclasse. |
definedFieldsCount |
Nombre de champs déclarés dans la classe elle-même. |
definedMethodsCount |
Nombre de méthodes définies dans la classe elle-même (localMethods). |
inheritedMethodsCount |
Sélecteurs connus de la superclasse que la classe ne définit pas elle-même. Une méthode redéfinie compte comme définie, pas comme héritée. |
superSendingMethods |
Méthodes locales dont l'AST contient un envoi de message dont le receveur est super. Un envoi à super n'importe où dans la méthode compte, même dans un bloc ou avec un autre sélecteur. |
longMethods |
Méthodes locales dont le code source a plus de lignes que longMethodThreshold. |
longMethodThreshold: |
Fixe le seuil. La valeur par défaut est définie dans initialize (15 lignes). |
Autres méthodes : on: (constructeur côté classe) et targetClass: (accesseur).
firstCommentOf: aMethodrenvoie le texte du premier commentaire de l'AST de la méthode (ast comments), ou une chaîne vide si la méthode n'a pas de commentaire.
- Cliquez sur l'icône en forme de cercle à côté d'une classe de test dans le System Browser, ou
- Ouvrez le Test Runner (menu World, Tools), sélectionnez
Documentor-Testset cliquez sur Run Selected.
Tous appellent (Documentor on: ...) report et vérifient le texte. Ils utilisent includesSubstring:, ce qui est acceptable dans les tests, car la règle « pas de manipulation de chaînes » concerne le code qui produit la sortie.
| Test | Comportement vérifié |
|---|---|
testReportMentionsClassName |
Le rapport contient DocFakeDog. |
testReportMentionsSuperClass |
Le rapport sur DocFakeDog contient DocFakeAnimal. |
testReportMentionsFields |
Le rapport contient le champ breed. |
testReportMentionsPackage |
Le rapport contient Documentor-Tests. |
testReportMentionsFirstParagraphOfComment |
Le rapport sur DocFakeAnimal contient le premier paragraphe du commentaire et ne contient pas le texte du second. |
testReportMentionsHierarchyPosition |
Le rapport sur DocFakeDog contient Object et DocFakeAnimal. |
testHierarchyShowsAllSuperClassesInOrder |
ProtoObject, Object et DocFakeAnimal apparaissent dans cet ordre. Chaque recherche commence après le résultat précédent, car Object est aussi une sous-chaîne de ProtoObject et DocFakeAnimal est déjà mentionné plus haut (« A subclass of »). |
testHierarchyListsDirectSubclasses |
Le rapport sur DocFakeAnimal contient sa sous-classe DocFakeDog. |
testReportListsMethodsWithFirstComment |
Le rapport sur DocFakeAnimal contient la section « Has methods », la méthode name et son premier commentaire. |
testReportWithoutCommentIsListed |
Une méthode sans commentaire (age) est quand même listée. |
| Test | Comportement vérifié |
|---|---|
testDepthOfInheritance |
DocFakeDog a une profondeur de 3 (DocFakeAnimal, Object, ProtoObject). |
testInheritedFieldsCount |
DocFakeDog hérite de 2 champs (name, age). |
testDefinedFieldsCount |
DocFakeDog définit 1 champ (breed). |
testDefinedMethodsCount |
Le nombre est égal à localMethods size. C'est circulaire, voir ci-dessous. |
testSuperSendingMethods |
Exactement une méthode de DocFakeDog envoie super, et c'est describe. |
testLongMethods |
Avec un seuil de 5, la seule méthode longue est longMethod. |
Documentor on: ProtoObjectéchoue, carreportInheritancedemande le nom d'une superclasse qui vautnil.reportMetricsaffiche les méthodes qui appellentsuperet les méthodes longues avec leprintStringde la collection, qui imprime les méthodes compilées sous la formeDocFakeDog>>#describe. Une liste avec un élément par méthode (comme dansreportMethods) serait plus propre.Documentorne permet pas de transmettre un seuil de méthode longue à sonClassMetrics. Il utilise toujours la valeur par défaut.reportMethodsécrit son titre et sa liste à l'intérieur d'un paragraphe. Cela fonctionne, mais la structure est inhabituelle.reportPackageécrit « in defined in package », une coquille à corriger. Les tests ne vérifient que le nom du package, donc corriger la formulation ne les cassera pas.testDefinedMethodsCountcalcule sa valeur attendue de la même façon que le code. La remplacer par le littéral3en fait une vraie vérification.inheritedMethodsCountn'a pas encore de test.- Les noms des classes d'AST et de parser (
OC*,MicrodownParser,MicParagraphBlock) dépendent de la version de Pharo.
Un visiteur (sous-classe de OCProgramNodeVisitor) qui signale les méthodes n'accédant à aucune variable d'instance, et celles qui n'accèdent ni à self ni aux variables d'instance. L'analyse sera dans ses propres classes, et Documentor ne fera que présenter les résultats.