Salesforce acaba de publicar un plugin para su CLI, denominado CLI Scanner.
En este artículo veremos qué es, para qué sirve, cómo utilizarlo y sus fortalezas y puntos de mejora.
¿Qué es?
- Salesforce CLI Scanner es un plug-in de la CLI de Salesforce (vaya frase!!). Esto implica que es invocable en la línea de comandos, y por tanto automatizable en un pipeline de CI/CD, o en tipo shell script, etc.
- Es un agregador, es decir, ejecuta motores de análisis estático de código, y ofrece los resultados obtenidos (incumplimientos en base a las reglas de los motores).
- Permite seleccionar las opciones para que el equipo de proyecto pueda limitar el ámbito de actuación.
- Permite añadir/eliminar reglas de los motores usados.
En el esquema de la documentación de Salesforce, vemos como el agregador tiene como inputs: el código fuente, las aplica las reglas de todos los motores de los que tiene constancia en su catálogo, y devuelve los incumplimientos que los diversos motores hayan encontrado.
En esta primera versión del Scanner, tenemos los 2 motores de reglas más usados por los desarrolladores:
- PMD (v6.22.0)
- ESlint (6.8.0)

Funciones del Scanner CLI
Sus funciones son básicamente 5 actualmente:
- Ejecutar un análisis de código fuente. Permite la selección de las categorías de validación (subconjuntos de reglas de un mismo ámbito) que queremos ejecutar, los ficheros de código fuente que queremos analizar, el formato de salida, etc.
- Listar todas las reglas disponibles en el catálogo. Obtenemos la lista de reglas que por lenguaje y/o por categoría a la que pertenecen. Al final de este artículo he incluido el listado de todas las reglas disponibles.
- Obtener la descripción y las características de una regla. Obtiene los atributos, como nombre, descripción, categoría, etc., de una regla concreta.
- Añadir una regla al catálogo. Mis ex-compañeros de un banco español muy conocido, estarán felices de esta capacidad ya que habían ampliado el conjunto de reglas de PMD.
- Eliminar una regla existente del catálogo.
Video demostrativo: instalación y uso básico
En el siguiente video intento mostrarte como instalar y como usarla, por ejemplo para analizar la aplicación Dreamhouse.
Ejemplos
Veamos algunos ejemplos para una mayor comprensión:
sfdx scanner:run --format table --target TestPropertyController.cls
: analiza el fichero respecto a todas las reglas del catálogo (solo serán aplicadas las de Apex), la salida la solicito en formato tabla en la salida estándar.

sfdx scanner:run --format table --target messageService.js --category "Design"
: analiza el fichero javascript indicado respecto a las reglas que pertenecen a la categoría Design de Javascript
sfdx scanner:rule:describe --rulename ExcessiveClassLength
: nos detalla las características de la regla, nos detalla en que circunstacia aplica, etc.:

Feedback – lo que más me ha gustado
- Esta herramienta simplifica todo el proceso de instalación de PMD y ESLint para automatización en una pipeline de CI/CD. Aunque antes ya era posible con las herramientas de PMD y Eslint, la facilidad de instalar/actualizar y usar el plugin es genial.
- Los requerimientos son básicos, y la instalación trivial, con tener la JDK 8 o superior y la CLI es suficiente. Ambos requerimientos son totalmente automatizables, pueden ser satisfechos al vuelo en un contenedor por ejemplo para tener escaneos de usar y tirar, completamente automatizado.
- Su mantenimiento al ser un plugin será realmente sencilla.
- Permite que un equipo de desarrollo unifique una herramienta más de manera sencilla mediante el uso de un plugin.
- Las opciones de utilizar JSON como formato de salida y la opción
--output
me parecen super interesantes. - La idea de un agregador, como ejecutor de motores me ha gustado mucho, ya que sienta las bases de una herramienta muy potente y abierta, en lugar de reinventar la rueda.
- Es una herramienta open source, podemos adaptarla y moldearla a nuestras necesidades.
Feedback – posibilidades de mejora
Esta es la 1a versión del scanner, y me parece una idea muy buena, de cara a las posibles ampliaciones con nuevos motores.
Aun así, en esta mi primera toma de contacto, estas son las pequeñas debilidades/inconvenientes que he encontrado:
- Conozco algunos proyectos que estandarizan una versión muy concreta de PMD, con lo que si la versión que está dentro del motor no es la misma, deben añadir y elimina las reglas que no coincidan entre versiones. Aunque como es automatizables será un esfuerzo inicial que valga la pena
- Si el fichero de código fuente no existe, no aparece ningún mensaje de error:

- No es posible analizar todos los ficheros de una aplicación ya que no hay buscar recursiva en directorios. Sin ser ningún experto, te paso el script que he utilizado para analizar el proyecto Dreamhouse desde su raíz
#! /bin/bash
#Directorio del proyecto
SourceCodeDir="/mnt/DATOS/workspace/dreamhouse-lwc"
#Analisis de ficheros Apex
find $SourceCodeDir -name "*.cls" -print0 | while read -d $'\0' file
do
echo -e "\nANALIZANDO $file"
#Aquí puedes limitar qué categorias, especificar el formato de salida etc.
sfdx scanner:run --format table --target $file
done
#Analisis de ficheros Javascript
find $SourceCodeDir -name "*.js" -print0 | while read -d $'\0' file
do
echo -e "\nANALIZANDO $file"
#Idem
sfdx scanner:run --format table --target $file
done
- Aparecen muchos errores debido a que no se reconocen las anotaciones propias y otras características de LWC.
- Hecho en falta la salida en formato HTML directamente.
Conclusiones
Este scanner establece las bases para un análisis estático de código con diversos motores, especialmente útil en un pipeline CI/CD, y que puede ser usado desde el primer día dado que cuenta con los 3 motores más usados actualmente.
Links interesantes
Anuncio disponibilidad de SFDX CLI Scanner: https://developer.salesforce.com/blogs/2020/10/improve-your-code-quality-with-the-salesforce-cli-scanner.html
Documentación oficial SFDX CLI Scanner: https://forcedotcom.github.io/sfdx-scanner/
Qué son los Plugins para la Salesforce CLI: https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_intro_what_is_plugin.htm
Aplicación Dreamhouse – como instalarla: https://github.com/trailheadapps/dreamhouse-lwc#installing-dreamhouse-using-a-developer-edition-org-or-a-trailhead-playground
Video – Análisis Código Estástico con PMD: https://youtu.be/I03y7xKQdCE
Repositorio de código del proyecto SFDX-Scanner: https://github.com/forcedotcom/sfdx-scanner
Artículos anterior sobre Análisis de código estático con PMD: Análisis de código Estático con PMD
Apéndice – Listado de reglas por lenguaje
Reglas para APEX y visualforce
Nombre Regla | Lenguaje | Categoría |
VfCsrf | visualforce | Security |
VfUnescapeEl | visualforce | Security |
ApexAssertionsShouldIncludeMessage | Apex | Best Practices |
ApexUnitTestClassShouldHaveAsserts | Apex | Best Practices |
ApexUnitTestMethodShouldHaveIsTestAnnotation | Apex | Best Practices |
ApexUnitTestShouldNotUseSeeAllDataTrue | Apex | Best Practices |
AvoidGlobalModifier | Apex | Best Practices |
AvoidLogicInTrigger | Apex | Best Practices |
DebugsShouldUseLoggingLevel | Apex | Best Practices |
AvoidDmlStatementsInLoops | Apex | Performance |
AvoidSoqlInLoops | Apex | Performance |
AvoidSoslInLoops | Apex | Performance |
ApexBadCrypto | Apex | Security |
ApexCRUDViolation | Apex | Security |
ApexCSRF | Apex | Security |
ApexDangerousMethods | Apex | Security |
ApexInsecureEndpoint | Apex | Security |
ApexOpenRedirect | Apex | Security |
ApexSharingViolations | Apex | Security |
ApexSOQLInjection | Apex | Security |
ApexSuggestUsingNamedCred | Apex | Security |
ApexXSSFromEscapeFalse | Apex | Security |
ApexXSSFromURLParam | Apex | Security |
ClassNamingConventions | Apex | Code Style |
IfElseStmtsMustUseBraces | Apex | Code Style |
IfStmtsMustUseBraces | Apex | Code Style |
FieldNamingConventions | Apex | Code Style |
ForLoopsMustUseBraces | Apex | Code Style |
FormalParameterNamingConventions | Apex | Code Style |
LocalVariableNamingConventions | Apex | Code Style |
MethodNamingConventions | Apex | Code Style |
OneDeclarationPerLine | Apex | Code Style |
PropertyNamingConventions | Apex | Code Style |
VariableNamingConventions | Apex | Code Style |
WhileLoopsMustUseBraces | Apex | Code Style |
AvoidDeeplyNestedIfStmts | Apex | Design |
CyclomaticComplexity | Apex | Design |
CognitiveComplexity | Apex | Design |
ExcessiveParameterList | Apex | Design |
ExcessivePublicCount | Apex | Design |
NcssConstructorCount | Apex | Design |
NcssMethodCount | Apex | Design |
NcssTypeCount | Apex | Design |
StdCyclomaticComplexity | Apex | Design |
TooManyFields | Apex | Design |
ApexCSRF | apex | Error Prone |
AvoidDirectAccessTriggerMap | apex | Error Prone |
AvoidHardcodingId | apex | Error Prone |
EmptyCatchBlock | apex | Error Prone |
EmptyIfStmt | apex | Error Prone |
EmptyStatementBlock | apex | Error Prone |
EmptyTryOrFinallyBlock | apex | Error Prone |
EmptyWhileStmt | apex | Error Prone |
MethodWithSameNameAsEnclosingClass | apex | Error Prone |
AvoidNonExistentAnnotations | apex | Error Prone |
TestMethodsMustBeInTestClasses | apex | Error Prone |
ApexDoc | Apex |
Para Javascript
Nombre Regla | Lenguaje | Categoria |
constructor-super | javascript | ECMAScript 6eslint |
for-direction | javascript | Possible Errorseslint |
getter-return | javascript | Possible Errorseslint |
no-async-promise-executor | javascript | Possible Errorseslint |
no-case-declarations | javascript | Best Practiceseslint |
no-class-assign | javascript | ECMAScript 6eslint |
no-compare-neg-zero | javascript | Possible Errorseslint |
no-cond-assign | javascript | Possible Errorseslint |
no-const-assign | javascript | ECMAScript 6eslint |
no-constant-condition | javascript | Possible Errorseslint |
no-control-regex | javascript | Possible Errorseslint |
no-debugger | javascript | Possible Errorseslint |
no-delete-var | javascript | Variableseslint |
no-dupe-args | javascript | Possible Errorseslint |
no-dupe-class-members | javascript | ECMAScript 6eslint |
no-dupe-keys | javascript | Possible Errorseslint |
no-duplicate-case | javascript | Possible Errorseslint |
no-empty | javascript | Possible Errorseslint |
no-empty-character-class | javascript | Possible Errorseslint |
no-empty-pattern | javascript | Best Practiceseslint |
no-ex-assign | javascript | Possible Errorseslint |
no-extra-boolean-cast | javascript | Possible Errorseslint |
no-extra-semi | javascript | Possible Errorseslint |
no-fallthrough | javascript | Best Practiceseslint |
no-func-assign | javascript | Possible Errorseslint |
no-global-assign | javascript | Best Practiceseslint |
no-inner-declarations | javascript | Possible Errorseslint |
no-invalid-regexp | javascript | Possible Errorseslint |
no-irregular-whitespace | javascript | Possible Errorseslint |
no-misleading-character-class | javascript | Possible Errorseslint |
no-mixed-spaces-and-tabs | javascript | Stylistic Issueseslint |
no-new-symbol | javascript | ECMAScript 6eslint |
no-obj-calls | javascript | Possible Errorseslint |
no-octal | javascript | Best Practiceseslint |
no-prototype-builtins | javascript | Possible Errorseslint |
no-redeclare | javascript | Best Practiceseslint |
no-regex-spaces | javascript | Possible Errorseslint |
no-self-assign | javascript | Best Practiceseslint |
no-shadow-restricted-names | javascript | Variableseslint |
no-sparse-arrays | javascript | Possible Errorseslint |
no-this-before-super | javascript | ECMAScript 6eslint |
no-undef | javascript | Variableseslint |
no-unexpected-multiline | javascript | Possible Errorseslint |
no-unreachable | javascript | Possible Errorseslint |
no-unsafe-finally | javascript | Possible Errorseslint |
no-unsafe-negation | javascript | Possible Errorseslint |
no-unused-labels | javascript | Best Practiceseslint |
no-unused-vars | javascript | Variableseslint |
no-useless-catch | javascript | Best Practiceseslint |
no-useless-escape | javascript | Best Practiceseslint |
no-with | javascript | Best Practiceseslint |
require-yield | javascript | ECMAScript 6eslint |
use-isnan | javascript | Possible Errorseslint |
valid-typeof | javascript | Possible Errorseslint |
constructor-super | javascript | ECMAScript 6eslint |
for-direction | javascript | Possible Errorseslint |
getter-return | javascript | Possible Errorseslint |
no-async-promise-executor | javascript | Possible Errorseslint |
no-case-declarations | javascript | Best Practiceseslint |
no-class-assign | javascript | ECMAScript 6eslint |
no-compare-neg-zero | javascript | Possible Errorseslint |
no-cond-assign | javascript | Possible Errorseslint |
no-const-assign | javascript | ECMAScript 6eslint |
no-constant-condition | javascript | Possible Errorseslint |
no-control-regex | javascript | Possible Errorseslint |
no-debugger | javascript | Possible Errorseslint |
no-delete-var | javascript | Variableseslint |
no-dupe-args | javascript | Possible Errorseslint |
no-dupe-class-members | javascript | ECMAScript 6eslint |
no-dupe-keys | javascript | Possible Errorseslint |
no-duplicate-case | javascript | Possible Errorseslint |
no-empty | javascript | Possible Errorseslint |
no-empty-character-class | javascript | Possible Errorseslint |
no-empty-pattern | javascript | Best Practiceseslint |
no-ex-assign | javascript | Possible Errorseslint |
no-extra-boolean-cast | javascript | Possible Errorseslint |
no-extra-semi | javascript | Possible Errorseslint |
no-fallthrough | javascript | Best Practiceseslint |
no-func-assign | javascript | Possible Errorseslint |
no-global-assign | javascript | Best Practiceseslint |
no-inner-declarations | javascript | Possible Errorseslint |
no-invalid-regexp | javascript | Possible Errorseslint |
no-irregular-whitespace | javascript | Possible Errorseslint |
no-misleading-character-class | javascript | Possible Errorseslint |
no-mixed-spaces-and-tabs | javascript | Stylistic Issueseslint |
no-new-symbol | javascript | ECMAScript 6eslint |
no-obj-calls | javascript | Possible Errorseslint |
no-octal | javascript | Best Practiceseslint |
no-prototype-builtins | javascript | Possible Errorseslint |
no-redeclare | javascript | Best Practiceseslint |
no-regex-spaces | javascript | Possible Errorseslint |
no-self-assign | javascript | Best Practiceseslint |
no-shadow-restricted-names | javascript | Variableseslint |
no-sparse-arrays | javascript | Possible Errorseslint |
no-this-before-super | javascript | ECMAScript 6eslint |
no-undef | javascript | Variableseslint |
no-unexpected-multiline | javascript | Possible Errorseslint |
no-unreachable | javascript | Possible Errorseslint |
no-unsafe-finally | javascript | Possible Errorseslint |
no-unsafe-negation | javascript | Possible Errorseslint |
no-unused-labels | javascript | Best Practiceseslint |
no-unused-vars | javascript | Variableseslint |
no-useless-catch | javascript | Best Practiceseslint |
no-useless-escape | javascript | Best Practiceseslint |
no-with | javascript | Best Practiceseslint |
require-yield | javascript | ECMAScript 6eslint |
vuse-isnan | javascript | Possible Errorseslint |
valid-typeof | javascript | Possible Errorseslint |
@lwc/lwc/no-api-reassignments | javascript | LWCeslint |
@lwc/lwc/no-async-operation | javascript | LWCeslint |
@lwc/lwc/no-deprecated | javascript | LWCeslint |
@lwc/lwc/no-inner-html | javascript | LWCeslint |
@lwc/lwc/no-leading-uppercase-api-name | javascript | LWCeslint |
@lwc/lwc/no-leaky-event-listeners | javascript | LWCeslint |
@lwc/lwc/valid-api | javascript | LWCeslint |
@lwc/lwc/valid-track | javascript | LWCeslint |
@lwc/lwc/valid-wire | javascript | LWCeslint |
Para Typescript
Nombre Regla | Lenguaje | Categoria |
constructor-super | typescript | ECMAScript 6eslint |
for-direction | typescript | Possible Errorseslint |
getter-return | typescript | Possible Errorseslint |
no-async-promise-executor | typescript | Possible Errorseslint |
no-case-declarations | typescript | Best Practiceseslint |
no-class-assign | typescript | ECMAScript 6eslint |
no-compare-neg-zero | typescript | Possible Errorseslint |
no-cond-assign | typescript | Possible Errorseslint |
no-const-assign | typescript | ECMAScript 6eslint |
no-constant-condition | typescript | Possible Errorseslint |
no-control-regex | typescript | Possible Errorseslint |
no-debugger | typescript | Possible Errorseslint |
no-delete-var | typescript | Variableseslint |
no-dupe-args | typescript | Possible Errorseslint |
no-dupe-class-members | typescript | ECMAScript 6eslint |
no-dupe-keys | typescript | Possible Errorseslint |
no-duplicate-case | typescript | Possible Errorseslint |
no-empty | typescript | Possible Errorseslint |
no-empty-character-class | typescript | Possible Errorseslint |
no-empty-pattern | typescript | Best Practiceseslint |
no-ex-assign | typescript | Possible Errorseslint |
no-extra-boolean-cast | typescript | Possible Errorseslint |
no-extra-semi | typescript | Possible Errorseslint |
no-fallthrough | typescript | Best Practiceseslint |
no-func-assign | typescript | Possible Errorseslint |
no-global-assign | typescript | Best Practiceseslint |
no-inner-declarations | typescript | Possible Errorseslint |
no-invalid-regexp | typescript | Possible Errorseslint |
no-irregular-whitespace | typescript | Possible Errorseslint |
no-misleading-character-class | typescript | Possible Errorseslint |
no-mixed-spaces-and-tabs | typescript | Stylistic Issueseslint |
no-new-symbol | typescript | ECMAScript 6eslint |
no-obj-calls | typescript | Possible Errorseslint |
no-octal | typescript | Best Practiceseslint |
no-prototype-builtins | typescript | Possible Errorseslint |
no-redeclare | typescript | Best Practiceseslint |
no-regex-spaces | typescript | Possible Errorseslint |
no-self-assign | typescript | Best Practiceseslint |
no-shadow-restricted-names | typescript | Variableseslint |
no-sparse-arrays | typescript | Possible Errorseslint |
no-this-before-super | typescript | ECMAScript 6eslint |
no-undef | typescript | Variableseslint |
no-unexpected-multiline | typescript | Possible Errorseslint |
no-unreachable | typescript | Possible Errorseslint |
no-unsafe-finally | typescript | Possible Errorseslint |
no-unsafe-negation | typescript | Possible Errorseslint |
no-unused-labels | typescript | Best Practiceseslint |
no-unused-vars | typescript | Variableseslint |
no-useless-catch | typescript | Best Practiceseslint |
no-useless-escape | typescript | Best Practiceseslint |
no-with | typescript | Best Practiceseslint |
require-yield | typescript | ECMAScript 6eslint |
use-isnan | typescript | Possible Errorseslint |
valid-typeof | typescript | Possible Errorseslint |
@typescript-eslint/adjacent-overload-signatures | typescript | Best Practiceseslint |
@typescript-eslint/await-thenable | typescript | Best Practiceseslint |
@typescript-eslint/ban-ts-ignore | typescript | Best Practiceseslint |
@typescript-eslint/ban-types | typescript | Best Practiceseslint |
@typescript-eslint/camelcase | typescript | Stylistic Issueseslint |
@typescript-eslint/class-name-casing | typescript | Best Practiceseslint |
@typescript-eslint/consistent-type-assertions | typescript | Best Practiceseslint |
@typescript-eslint/explicit-function-return-type | typescript | Stylistic Issueseslint |
@typescript-eslint/interface-name-prefix | typescript | Stylistic Issueseslint |
@typescript-eslint/member-delimiter-style | typescript | Stylistic Issueseslint |
@typescript-eslint/no-array-constructor | typescript | Stylistic Issueseslint |
@typescript-eslint/no-empty-function | typescript | Best Practiceseslint |
@typescript-eslint/no-empty-interface | typescript | Best Practiceseslint |
@typescript-eslint/no-explicit-any | typescript | Best Practiceseslint |
@typescript-eslint/no-for-in-array | typescript | Best Practiceseslint |
@typescript-eslint/no-inferrable-types | typescript | Best Practiceseslint |
@typescript-eslint/no-misused-new | typescript | Best Practiceseslint |
@typescript-eslint/no-misused-promises | typescript | Best Practiceseslint |
@typescript-eslint/no-namespace | typescript | Best Practiceseslint |
@typescript-eslint/no-non-null-assertion | typescript | Stylistic Issueseslint |
@typescript-eslint/no-this-alias | typescript | Best Practiceseslint |
@typescript-eslint/no-unnecessary-type-assertion | typescript | Best Practiceseslint |
@typescript-eslint/no-unused-vars | typescript | Variableseslint |
@typescript-eslint/no-use-before-define | typescript | Variableseslint |
@typescript-eslint/no-var-requires | typescript | Best Practiceseslint |
@typescript-eslint/prefer-includes | typescript | Best Practiceseslint |
@typescript-eslint/prefer-namespace-keyword | typescript | Best Practiceseslint |
@typescript-eslint/prefer-regexp-exec | typescript | Best Practiceseslint |
@typescript-eslint/prefer-string-starts-ends-with | typescript | Best Practiceseslint |
@typescript-eslint/require-await | typescript | Best Practiceseslint |
@typescript-eslint/triple-slash-reference | typescript | Best Practiceseslint |
@typescript-eslint/type-annotation-spacing | typescript | Stylistic Issueseslint |
@typescript-eslint/unbound-method | typescript | Best Practiceseslint |
Deja una respuesta