Cualquier ingeniero se esfuerza por hacer que su proceso de trabajo sea lo más optimizado posible. Como desarrolladores móviles de iOS, a menudo tenemos que trabajar con estructuras de lenguaje uniformes. Apple está mejorando las herramientas para desarrolladores haciendo un gran esfuerzo para que sea conveniente para nosotros programar: resaltado del lenguaje, métodos de autocompletado y muchas otras funciones IDE que permiten que nuestros dedos se mantengan al día con las ideas en nuestras cabezas.

¿Qué hace un ingeniero cuando falta la herramienta requerida? Es cierto, él hará todo por sí mismo! Anteriormente
hablamos sobre la creación de nuestras herramientas personalizadas, ahora hablemos sobre cómo modificar Xcode y hacer que funcione de acuerdo con sus reglas.
Tomamos la tarea de
JIRA Swift e hicimos una herramienta que convierte
si se deja en un
guardia equivalente
, deja construir.

Desde la novena versión, Xcode proporciona un nuevo mecanismo de refactorización que puede convertir el código localmente, dentro del mismo archivo fuente de Swift o globalmente cuando cambia el nombre de un método o propiedad que ocurre en varios archivos, incluso si están en diferentes idiomas.
La refactorización local se implementa completamente en el compilador y el marco de SourceKit, la característica se encuentra en el repositorio Swift de código abierto y está escrita en C ++. La modificación de la refactorización global es actualmente inaccesible para la gente común, porque la base del código Xcode está cerrada. Por lo tanto, nos detendremos en la historia local y hablaremos sobre cómo repetir nuestra experiencia.
Lo que necesita para crear su propia herramienta para la refactorización local:
- Entendiendo C ++
- Conocimientos básicos del compilador.
- Comprender qué es AST y cómo trabajar con él
- Código fuente rápido
- Guía swift / docs / refactoring / SwiftLocalRefactoring.md
- Mucha paciencia
Un poco sobre AST
Un poco de conceptos básicos teóricos antes de sumergirse en la práctica. Echemos un vistazo a cómo funciona la arquitectura del compilador Swift. En primer lugar, el compilador es responsable de transformar el código en código máquina ejecutable.

De las etapas de transformación presentadas, la más interesante para nosotros es la generación de un árbol de sintaxis abstracta (AST), un gráfico en el que los vértices son operadores y las hojas son sus operandos.

Los árboles de sintaxis se usan en el analizador sintáctico. AST se usa como una representación interna en el compilador / intérprete de un programa de computadora para optimizar y generar código.
Después de que se genera el AST, se realiza el análisis para crear el AST con una verificación de tipo que se ha traducido al lenguaje intermedio rápido. SIL se convierte, optimiza, degrada a LLVM IR, que finalmente se compila en código de máquina.
Para crear una herramienta de refactorización, necesitamos comprender AST y poder trabajar con ella. Por lo tanto, la herramienta podrá funcionar correctamente con partes del código que queremos procesar.
Para generar el AST de un archivo, ejecute el comando: swiftc -dump-ast
MyFile.swift
A continuación se muestra la salida a la consola AST de la función if let , que se mencionó anteriormente.

Hay tres tipos principales de nodos en Swift AST:
- declaraciones (subclases de tipo Decl),
- expresiones (subclases de tipo Expr),
- operadores (subclases de tipo Stmt).
Corresponden a las tres entidades que se utilizan en el lenguaje Swift. Los nombres de funciones, estructuras, parámetros son declaraciones. Las expresiones son entidades que devuelven un valor; por ejemplo, llamando a funciones. Los operadores son partes del lenguaje que definen el flujo de control de la ejecución del código, pero no devuelven un valor (por ejemplo, if o do-catch).
Este es un mínimo suficiente que necesita saber sobre AST para su próximo trabajo.
Cómo funciona la herramienta de refactorización en teoría
Para implementar herramientas de refactorización, necesita información específica sobre el área del código que va a cambiar. Los desarrolladores cuentan con entidades auxiliares que acumulan datos. El primero, ResolvedCursorInfo (refactorización basada en el cursor), le dice si estamos al comienzo de una expresión. Si es así, se devuelve el objeto compilador correspondiente de esta expresión. La segunda entidad, RangeInfo (refactorización basada en rango), encapsula datos sobre el rango original (por ejemplo, cuántos puntos de entrada y salida tiene).
La refactorización basada en el cursor se inicia mediante la ubicación del cursor en el archivo fuente. Las acciones de refactorización implementan los métodos que utiliza el mecanismo de refactorización para mostrar las acciones disponibles en el IDE y realizar transformaciones. Ejemplos de acciones basadas en el cursor: Saltar a definición, ayuda rápida, etc.

Considere las acciones habituales desde el lado técnico:
- Cuando selecciona una ubicación del editor Xcode, se realiza una solicitud a sourcekitd (el marco responsable de resaltar, completar el código, etc.) para mostrar las acciones de refactorización disponibles.
- El objeto ResolvedCursorInfo solicita cada acción disponible para verificar si esta acción se aplica al código seleccionado.
- La lista de acciones aplicables se devuelve como una respuesta de sourcekitd y se muestra en Xcode.
- Xcode aplica los cambios a la herramienta de refactorización.
La refactorización basada en rango se inicia seleccionando un rango continuo de código en el archivo fuente.

En este caso, la herramienta de refactorización pasará por una cadena de llamada similar a la descrita. La diferencia es que cuando se implementa, la entrada es RangeInfo en lugar de ResolvedCursorInfo. Los lectores interesados pueden consultar
Refactoring.cpp para obtener más información sobre ejemplos de kits de herramientas de Apple.
Y ahora a la práctica de crear una herramienta.
Preparación
En primer lugar, debe descargar y compilar el compilador Swift. Las instrucciones detalladas se encuentran en el repositorio oficial (
readme.md ). Estos son los comandos clave para la clonación de código:
mkdir swift-source cd swift-source git clone https:
Cmake se usa para describir la estructura y las dependencias del proyecto. Al usarlo, puede generar un proyecto para Xcode (más conveniente) o para ninja (más rápido) debido a uno de los comandos:
./utils/build-script --debug --xcode
o
swift/utils/build-script --debug-debuginfo
La compilación exitosa requiere la última versión de Xcode beta (10.2.1 en el momento de la redacción), disponible en el
sitio web oficial de Apple . Para usar el nuevo Xcode para construir el proyecto, debe registrar la ruta utilizando la utilidad xcode-select:
sudo xcode-select -s /Users/username/Xcode.app
Si usamos el indicador --xcode para compilar el proyecto para Xcode, respectivamente, luego de varias horas de compilación (obtuvimos un poco más de dos) en la carpeta de compilación, encontraremos el archivo Swift.xcodeproj. Al abrir el proyecto, veremos el Xcode familiar con indexación, puntos de interrupción.
Para crear un nuevo instrumento, necesitamos agregar el código con la lógica del instrumento al archivo: lib / IDE / Refactoring.cpp y definir dos métodos, isApplicable y performChange. En el primer método, decidimos si generar la opción de refactorización para el código seleccionado. Y en el segundo, cómo convertir el código seleccionado para aplicar la refactorización.
Una vez realizada la preparación, queda por implementar los siguientes pasos:
- Desarrolle la lógica de la herramienta (el desarrollo se puede hacer de varias maneras: a través de la cadena de herramientas, a través de Ninja, a través de Xcode; todas las opciones se describirán a continuación)
- Implemente dos métodos: isApplicable y performChange (son responsables del acceso a la herramienta y su funcionamiento)
- Diagnostique y pruebe la herramienta terminada antes de enviar el RP al repositorio oficial de Swift.
Pruebe el funcionamiento de la herramienta a través de la cadena de herramientas
Este método de desarrollo le llevará mucho tiempo debido al largo ensamblaje de componentes, pero el resultado es visible de inmediato en Xcode, la forma de verificarlo manualmente.
Para comenzar, construyamos la cadena de herramientas Swift usando el comando:
./utils/build-toolchain some_bundle_id
Compilar la cadena de herramientas llevará incluso más tiempo que compilar el compilador y las dependencias. La salida es el archivo swift-LOCAL-aaaa-mm-dd.xctoolchain en la carpeta swift-nightly-install, que debe transferir a Xcode: / Library / Developer / Toolchains /. A continuación, en la configuración de IDE, seleccione la nueva cadena de herramientas, reinicie Xcode.

Seleccione un fragmento de código que la herramienta debe procesar y búsquelo en el menú contextual.
Desarrollo a través de pruebas con Ninja
Si el proyecto fue creado para Ninja y usted eligió el camino TDD, entonces el desarrollo a través de pruebas con Ninja es una de las opciones que más le convengan. Contras: no puede establecer puntos de interrupción, como en el desarrollo a través de Xcode.
Por lo tanto, debemos verificar que la nueva herramienta se muestre en Xcode cuando el usuario selecciona la construcción de protección en el código fuente. Escribimos la prueba en el archivo existente test / refactoring / RefactoringKind / basic.swift:
func testConvertToGuardExpr(idxOpt: Int?) { if let idx = idxOpt { print(idx) } }
Indicamos que al resaltar código entre 266 columnas de la fila 3 y 268 columnas de la fila 4, esperamos la aparición de un elemento de menú con una nueva herramienta.
El uso del script
lit.py puede proporcionar comentarios más rápidos a su ciclo de desarrollo. Puede especificar el traje de prueba de interés. En nuestro caso, esta suite será RefactoringKind:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/
Como resultado, solo se iniciarán las pruebas de este archivo. Su implementación tomará un par de decenas de segundos. Más adelante se discutirá más información sobre lit.py en la sección Diagnóstico y prueba.
La prueba falla, lo cual es normal para el paradigma TDD. Después de todo, hasta ahora no hemos escrito una sola línea de código con la lógica de la herramienta.
Desarrollo a través de depuración y Xcode
Y finalmente, el último método de desarrollo cuando el proyecto fue construido bajo Xcode. La ventaja principal es la capacidad de establecer puntos de interrupción y controlar la depuración.
Al compilar un proyecto en Xcode, el archivo Swift.xcodeproj se crea en la carpeta build / Xcode-DebugAssert / swift-macosx-x86_64 /. Cuando abre este archivo por primera vez, es mejor elegir crear esquemas manualmente para generar ALL_BUILD y refactorizar rápidamente:

Luego, construimos el proyecto con ALL_BUILD una vez, luego de eso usamos el esquema de refactorización rápida.
La herramienta de refactorización se compila en un archivo ejecutable separado: swift-refactor. La ayuda para este archivo se puede mostrar con el indicador –help. Los parámetros más interesantes para nosotros son:
-source-filename=<string>
Se pueden especificar en el esquema como argumentos. Ahora puede establecer puntos de interrupción para detenerse en lugares de interés al iniciar la herramienta. De la manera habitual, usando los comandos
p y
po en la consola Xcode, muestra los valores de las variables correspondientes.

Implementación IsApplicable
El método isApplicable acepta un ResolvedRangeInfo con información sobre los nodos AST del fragmento de código seleccionado en la entrada. En la salida del método, se decide si mostrar la herramienta o no en el menú contextual de Xcode. La interfaz completa de ResolvedRangeInfo se puede encontrar en el
archivo include / swift / IDE / Utils.h .
Considere los campos de la clase ResolvedRangeInfo que son más útiles en nuestro caso:
- RangeKind: lo primero que debe hacer es verificar el tipo de área seleccionada. Si el área no es válida (no es válida), puede devolver falso. Si el tipo nos conviene, por ejemplo, SingleStatement o MultiStatement, continúe;
- ContainedNodes: una matriz de elementos AST que se incluyen en el rango seleccionado. Queremos asegurarnos de que el usuario seleccione el rango en el que entra la construcción if let. Para hacer esto, tomamos el primer elemento de la matriz y verificamos que este elemento corresponde a IfStmt (la clase que define el nodo AST del nodo de declaración del subtipo if). A continuación, ver condición. Para simplificar la implementación, mostraremos la herramienta solo para expresiones con una condición. Por el tipo de condición (CK_PatternBinding) determinamos que esto se deja.

Para probar es aplicable, agregue el código de muestra al archivo
test / refactoring / RefactoringKind / basic.swift .

Para que la prueba simule una llamada a nuestra herramienta, debe agregar una línea en el archivo
tools / swift-refactor / swift-refactor.cpp .

Implementamos performChange
Se llama a este método cuando se selecciona una herramienta de refactorización en el menú contextual. El método tiene acceso a ResolvedRangeInfo, así como en isApplicable. Usamos ResolvedRangeInfo y escribimos la lógica de la herramienta de conversión de código.
Al generar código para tokens estáticos (regulados por la sintaxis del lenguaje), puede usar entidades del espacio de nombres tok. Por ejemplo, para la palabra clave guard, use tok :: kw_guard. Para los tokens dinámicos (modificados por el desarrollador, por ejemplo, el nombre de la función), debe seleccionarlos de la matriz de elementos AST.
Para determinar dónde se inserta el código convertido, usamos el rango completo seleccionado usando la construcción RangeInfo.ContentRange.

Diagnósticos y pruebas
Antes de terminar de trabajar en una herramienta, debe verificar la corrección de su trabajo nuevamente. Las pruebas nos ayudarán nuevamente. Las pruebas se pueden ejecutar de una en una o con todos los ámbitos disponibles. La forma más fácil de ejecutar todo el conjunto de pruebas de Swift es con el comando --test en utils / build-script, que ejecutará el conjunto de pruebas principal. El uso de utils / build-script reconstruirá todos los objetivos, lo que puede aumentar significativamente el tiempo del ciclo de depuración.
Asegúrese de ejecutar utils / build-script --validation-test validation tests antes de realizar cambios importantes en el compilador o API.
Hay otra forma de ejecutar todas las pruebas unitarias del compilador: ninja, ninja check-swift desde build / preset / swift-macosx-x86_64. Tardará unos 15 minutos.
Y finalmente, la opción cuando necesita ejecutar pruebas por separado. Para invocar directamente el script lit.py desde LLVM, debe configurarlo para usar el directorio de compilación local. Por ejemplo:
% $ {LLVM_SOURCE_ROOT} /utils/lit/lit.py -sv $ {SWIFT_BUILD_DIR} / test-macosx-x86_64 / Parse /
Esto ejecutará las pruebas en el directorio 'test / Parse /' para macOS de 64 bits. La opción -sv proporciona un indicador de ejecución de prueba y muestra los resultados de solo pruebas fallidas.
Lit.py tiene algunas otras características útiles, como pruebas de tiempo y pruebas de latencia. Puede ver estas y otras funciones con lit.py -h. Lo más útil se puede encontrar
aquí .
Para ejecutar una prueba, escriba:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/basic.swift
Si necesitamos extraer los últimos cambios del compilador, entonces debemos actualizar todas las dependencias y hacer una nueva versión. Para actualizar, ejecute ./utils/update-checkout.
Conclusiones
Logramos nuestro objetivo: crear una herramienta que no estaba previamente en el IDE para optimizar el trabajo. Si también tiene ideas sobre cómo mejorar los productos de Apple y hacer la vida más fácil para toda la comunidad de iOS, siéntase libre de tomar la marca contraria, ¡porque es más fácil de lo que parece a primera vista!
En 2015, Apple subió el código fuente de Swift al dominio público, lo que permitió sumergirse en los detalles de implementación de su compilador. Además, con Xcode 9, puede agregar herramientas de refactorización locales. Un conocimiento básico de C ++ y un dispositivo compilador es suficiente para que su IDE favorito sea un poco más conveniente.
La experiencia descrita fue útil para nosotros: además de crear una herramienta que simplifica el proceso de desarrollo, obtuvimos un conocimiento realmente completo del lenguaje. Una caja de Pandora ligeramente abierta con procesos de bajo nivel le permite ver las tareas diarias desde un nuevo ángulo.
¡Esperamos que el conocimiento adquirido también enriquezca su comprensión del desarrollo!
El material fue coescrito con
@victoriaqb : Victoria Kashlina, desarrolladora de iOS.
Fuentes
- Dispositivo compilador rápido. Parte 2
- ¿Cómo construir una herramienta basada en compilador Swift? La guía paso a paso.
- Volcar el Swift AST para un proyecto iOS
- Presentamos el probador de estrés sourcekitd
- Prueba rápida
- [SR-5744] Acción de refactorización para convertir if-let en guard-let y viceversa # 24566