Qualquer engenheiro se esforça para tornar seu processo de trabalho o mais otimizado possível. Como desenvolvedores de dispositivos móveis para iOS, muitas vezes precisamos trabalhar com estruturas de linguagem uniformes. A Apple está aprimorando as ferramentas de desenvolvedor, envidando muito esforço para facilitar a programação: realce de linguagem, métodos de preenchimento automático e muitos outros recursos de IDE permitem que nossos dedos acompanhem as ideias em nossas cabeças.

O que um engenheiro faz quando falta a ferramenta necessária? É verdade que ele fará tudo sozinho! Anteriormente,
falamos sobre a criação de nossas ferramentas personalizadas, agora vamos falar sobre como modificar o Xcode e fazê-lo funcionar de acordo com suas regras.
Pegamos a tarefa do
JIRA Swift e fizemos uma ferramenta que
se converte
se deixada em um
guarda equivalente
, deixe construir.

Desde a nona versão, o Xcode fornece um novo mecanismo de refatoração que pode converter código localmente, no mesmo arquivo de origem Swift ou globalmente, quando você renomeia um método ou propriedade que ocorre em vários arquivos, mesmo que estejam em idiomas diferentes.
A refatoração local é totalmente implementada no compilador e na estrutura do SourceKit, o recurso está localizado no repositório Swift de código aberto e é escrito em C ++. Atualmente, a modificação da refatoração global está inacessível para as pessoas comuns, porque a base de código do Xcode está fechada. Portanto, abordaremos a história local e falaremos sobre como repetir nossa experiência.
O que você precisa para criar sua própria ferramenta para refatoração local:
- Noções básicas sobre C ++
- Conhecimento básico do compilador
- Entendendo o que é AST e como trabalhar com ele
- Código fonte Swift
- Guia swift / docs / refatoração / SwiftLocalRefactoring.md
- Muita paciência
Um pouco sobre a AST
Um pouco de princípios teóricos antes de mergulhar na prática. Vamos dar uma olhada em como a arquitetura do compilador Swift funciona. Primeiro de tudo, o compilador é responsável por transformar o código em código de máquina executável.

Dos estágios de transformação apresentados, o mais interessante para nós é a geração de uma árvore sintática abstrata (AST) - um gráfico no qual os vértices são operadores e as folhas são seus operandos.

Árvores de sintaxe são usadas no analisador. O AST é usado como uma representação interna no compilador / intérprete de um programa de computador para otimizar e gerar código.
Depois que o AST é gerado, a análise é realizada para criar o AST com uma verificação de tipo que foi traduzida para o Swift Intermediate Language. O SIL é convertido, otimizado e rebaixado para LLVM IR, que finalmente é compilado no código da máquina.
Para criar uma ferramenta de refatoração, precisamos entender o AST e poder trabalhar com ele. Portanto, a ferramenta poderá operar corretamente com partes do código que queremos processar.
Para gerar o AST de um arquivo, execute o comando: swiftc -dump-ast
MyFile.swift
Abaixo está a saída para o console AST da função if let , mencionada anteriormente.

Existem três tipos principais de nós no Swift AST:
- declarações (subclasses do tipo Decl),
- expressões (subclasses do tipo Expr),
- operadores (subclasses do tipo Stmt).
Eles correspondem às três entidades que são usadas na própria linguagem Swift. Nomes de funções, estruturas, parâmetros são declarações. Expressões são entidades que retornam um valor; por exemplo, chamando funções. Operadores são partes da linguagem que definem o fluxo de controle da execução do código, mas não retornam um valor (por exemplo, se ou captura).
Este é um mínimo suficiente que você precisa saber sobre a AST para seu próximo trabalho.
Como a ferramenta de refatoração funciona em teoria
Para implementar ferramentas de refatoração, você precisa de informações específicas sobre a área do código que você irá alterar. Os desenvolvedores são fornecidos com entidades auxiliares que acumulam dados. O primeiro, ResolvedCursorInfo (refatoração baseada em cursor), informa se estamos no início de uma expressão. Nesse caso, o objeto de compilador correspondente dessa expressão é retornado. A segunda entidade, RangeInfo (refatoração baseada em intervalo), encapsula dados sobre o intervalo original (por exemplo, quantos pontos de entrada e saída ele possui).
A refatoração baseada no cursor é iniciada pelo local do cursor no arquivo de origem. As ações de refatoração implementam os métodos que o mecanismo de refatoração usa para exibir ações disponíveis no IDE e executar transformações. Exemplos de ações baseadas no cursor: Salte para a definição, ajuda rápida, etc.

Considere as ações usuais do lado técnico:
- Quando você seleciona um local no editor do Xcode, uma solicitação é feita ao sourcekitd (a estrutura responsável pelo realce, conclusão do código etc.) para exibir as ações de refatoração disponíveis.
- Cada ação disponível é solicitada pelo objeto ResolvedCursorInfo para verificar se essa ação se aplica ao código selecionado.
- A lista de ações aplicáveis é retornada como uma resposta do sourcekitd e exibida no Xcode.
- O Xcode aplica as alterações na ferramenta de refatoração.
A refatoração baseada em intervalo é iniciada selecionando um intervalo contínuo de código no arquivo de origem.

Nesse caso, a ferramenta de refatoração passará por uma cadeia de chamadas semelhante descrita. A diferença é que, quando implementada, a entrada é RangeInfo em vez de ResolvedCursorInfo. Os leitores interessados podem consultar
Refactoring.cpp para obter mais informações sobre exemplos de kits de ferramentas da Apple.
E agora para a prática de criar uma ferramenta.
Preparação
Primeiro de tudo, você precisa baixar e compilar o compilador Swift. Instruções detalhadas estão no repositório oficial (
leia-me.md ). Aqui estão os principais comandos para a clonagem de código:
mkdir swift-source cd swift-source git clone https:
Cmake é usado para descrever a estrutura e dependências do projeto. Usando-o, você pode gerar um projeto para o Xcode (mais conveniente) ou para o ninja (mais rápido) devido a um dos comandos:
./utils/build-script --debug --xcode
ou
swift/utils/build-script --debug-debuginfo
A compilação bem-sucedida requer a versão mais recente do Xcode beta (10.2.1 no momento da redação deste documento) - disponível no
site oficial da Apple . Para usar o novo Xcode para criar o projeto, é necessário registrar o caminho usando o utilitário xcode-select:
sudo xcode-select -s /Users/username/Xcode.app
Se usamos o sinalizador --xcode para criar o projeto para o Xcode, respectivamente, depois de várias horas de compilação (temos um pouco mais de duas) na pasta build, encontraremos o arquivo Swift.xcodeproj. Ao abrir o projeto, veremos o Xcode familiar com indexação e pontos de interrupção.
Para criar um novo instrumento, precisamos adicionar o código com a lógica do instrumento ao arquivo: lib / IDE / Refactoring.cpp e definir dois métodos, isApplicable e performChange. No primeiro método, decidimos se a opção de refatoração deve ser gerada para o código selecionado. E no segundo - como converter o código selecionado para aplicar a refatoração.
Após a preparação, resta implementar as seguintes etapas:
- Desenvolva a lógica da ferramenta (o desenvolvimento pode ser feito de várias maneiras - através da cadeia de ferramentas, através do Ninja, através do Xcode; todas as opções serão descritas abaixo)
- Implemente dois métodos: isApplicable e performChange (eles são responsáveis pelo acesso à ferramenta e sua operação)
- Diagnostique e teste a ferramenta pronta antes de enviar o PR ao repositório oficial do Swift.
Teste a operação da ferramenta através da cadeia de ferramentas
Esse método de desenvolvimento levará muito tempo devido à longa montagem de componentes, mas o resultado é imediatamente visível no Xcode - a maneira de verificá-lo manualmente.
Para começar, vamos construir a cadeia de ferramentas Swift usando o comando:
./utils/build-toolchain some_bundle_id
Compilar o conjunto de ferramentas levará ainda mais tempo do que compilar o compilador e as dependências. A saída é o arquivo swift-LOCAL-aaaa-mm-dd.xctoolchain na pasta de instalação noturna rápida, que você precisa transferir para o Xcode: / Library / Developer / Toolchains /. Em seguida, nas configurações do IDE, selecione a nova cadeia de ferramentas, reinicie o Xcode.

Selecione um pedaço de código que a ferramenta deve processar e procure a ferramenta no menu de contexto.
Desenvolvimento através de testes com Ninja
Se o projeto foi construído para o Ninja e você escolheu o caminho do TDD, o desenvolvimento através de testes com o Ninja é uma das opções que mais lhe agradam. Contras - você não pode definir pontos de interrupção, como no desenvolvimento através do Xcode.
Portanto, precisamos verificar se a nova ferramenta é exibida no Xcode quando o usuário seleciona a construção de guarda no código-fonte. Escrevemos o teste no arquivo existente test / refactoring / RefactoringKind / basic.swift:
func testConvertToGuardExpr(idxOpt: Int?) { if let idx = idxOpt { print(idx) } }
Indicamos que, ao destacar o código entre 266 linhas da coluna 3 e 268 linhas da coluna 4, esperamos o aparecimento de um item de menu com uma nova ferramenta.
O uso do script
lit.py pode fornecer feedback mais rápido ao seu ciclo de desenvolvimento. Você pode especificar o naipe de interesse de seu interesse. No nosso caso, esse conjunto será o RefactoringKind:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/
Como resultado, testes somente deste arquivo serão iniciados. Sua implementação levará algumas dezenas de segundos. Mais informações sobre lit.py serão discutidas mais adiante na seção Diagnósticos e testes.
O teste falha, o que é normal para o paradigma TDD. Afinal, até agora não escrevemos uma única linha de código com a lógica da ferramenta.
Desenvolvimento através de depuração e Xcode
E, finalmente, o último método de desenvolvimento quando o projeto foi construído no Xcode. A principal vantagem é a capacidade de definir pontos de interrupção e controlar a depuração.
Ao criar um projeto no Xcode, o arquivo Swift.xcodeproj é criado na pasta build / Xcode-DebugAssert / swift-macosx-x86_64 /. Quando você abre esse arquivo pela primeira vez, é melhor optar por criar esquemas manualmente para gerar ALL_BUILD e refatorar você mesmo rapidamente:

Em seguida, criamos o projeto com ALL_BUILD uma vez, depois usamos o esquema swift-refactor.
A ferramenta de refatoração é compilada em um arquivo executável separado - swift-refactor. A ajuda para esse arquivo pode ser exibida usando o sinalizador –help. Os parâmetros mais interessantes para nós são:
-source-filename=<string>
Eles podem ser especificados no esquema como argumentos. Agora você pode definir pontos de interrupção para parar em locais de interesse ao iniciar a ferramenta. Da maneira usual, usando os comandos
p e
po no console do Xcode, ele exibe os valores das variáveis correspondentes.

Implementação IsApplicable
O método isApplicable aceita um ResolvedRangeInfo com informações sobre nós AST do fragmento de código selecionado na entrada. Na saída do método, é decidido se a ferramenta é exibida ou não no menu de contexto do Xcode. A interface ResolvedRangeInfo completa pode ser encontrada no
arquivo include / swift / IDE / Utils.h .
Considere os campos da classe ResolvedRangeInfo que são mais úteis em nosso caso:
- RangeKind - a primeira coisa a fazer é verificar o tipo da área selecionada. Se a área for inválida (Inválida), você poderá retornar falso. Se o tipo nos convém, por exemplo, SingleStatement ou MultiStatement, prossiga;
- ContainedNodes - uma matriz de elementos AST que se enquadram no intervalo selecionado. Queremos garantir que o usuário selecione o intervalo em que a construção if let entra. Para fazer isso, pegamos o primeiro elemento da matriz e verificamos que esse elemento corresponde a IfStmt (a classe que define o nó AST do nó de instrução do subtipo if). Em seguida, consulte a condição. Para simplificar a implementação, produziremos a ferramenta apenas para expressões com uma condição. Pelo tipo de condição (CK_PatternBinding), determinamos que isso é permitido.

Para testar isApplicable, adicione o código de amostra ao arquivo
test / refactoring / RefactoringKind / basic.swift .

Para que o teste simule uma chamada para nossa ferramenta, você precisa adicionar uma linha no arquivo
tools / swift-refactor / swift-refactor.cpp .

Implementamos performChange
Este método é chamado quando uma ferramenta de refatoração é selecionada no menu de contexto. O método tem acesso a ResolvedRangeInfo, bem como em isApplicable. Usamos ResolvedRangeInfo e escrevemos a lógica da ferramenta de conversão de código.
Ao gerar código para tokens estáticos (regulados pela sintaxe do idioma), você pode usar entidades do espaço para nome do tok. Por exemplo, para a palavra-chave guard, use tok :: kw_guard. Para tokens dinâmicos (modificados pelo desenvolvedor, por exemplo, o nome da função), é necessário selecioná-los na matriz de elementos AST.
Para determinar onde o código convertido é inserido, usamos o intervalo completo selecionado usando a construção RangeInfo.ContentRange.

Diagnósticos e testes
Antes de terminar de trabalhar em uma ferramenta, é necessário verificar novamente a correção do seu trabalho. Os testes nos ajudarão novamente. Os testes podem ser executados um de cada vez ou com todos os escopos disponíveis. A maneira mais fácil de executar todo o conjunto de testes Swift é com o comando --test em utils / build-script, que executará o conjunto de testes principal. O uso de utils / build-script reconstruirá todos os destinos, o que pode aumentar significativamente o tempo do ciclo de depuração.
Certifique-se de executar os testes de validação utils / build-script --validation-test antes de fazer grandes alterações no compilador ou na API.
Existe outra maneira de executar todos os testes de unidade do compilador - através do ninja, ninja check-swift em build / preset / swift-macosx-x86_64. Isso levará cerca de 15 minutos.
E, finalmente, a opção quando você precisa executar testes separadamente. Para chamar diretamente o script lit.py do LLVM, você deve configurá-lo para usar o diretório de construção local. Por exemplo:
% $ {LLVM_SOURCE_ROOT} /utils/lit/lit.py -sv $ {SWIFT_BUILD_DIR} / test-macosx-x86_64 / Parse /
Isso executará os testes no diretório 'test / Parse /' do macOS de 64 bits. A opção -sv fornece um indicador de execução de teste e mostra os resultados apenas de testes com falha.
O Lit.py possui outros recursos úteis, como testes de temporização e latência. Você pode visualizar esses e outros recursos com lit.py -h. O mais útil pode ser encontrado
aqui .
Para executar um teste, escreva:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/basic.swift
Se precisarmos obter as alterações mais recentes do compilador, precisamos atualizar todas as dependências e fazer uma nova reformulação. Para atualizar, execute ./utils/update-checkout.
Conclusões
Conseguimos atingir nosso objetivo - criar uma ferramenta que não estava anteriormente no IDE para otimizar o trabalho. Se você também tiver idéias sobre como melhorar os produtos Apple e facilitar a vida de toda a comunidade iOS, sinta-se à vontade para assumir a contra-marca, porque é mais fácil do que parece à primeira vista!
Em 2015, a Apple carregou o código-fonte Swift para o domínio público, o que tornou possível mergulhar nos detalhes da implementação de seu compilador. Além disso, com o Xcode 9, você pode adicionar ferramentas de refatoração locais. Um conhecimento básico de C ++ e um dispositivo compilador é suficiente para tornar seu IDE favorito um pouco mais conveniente.
A experiência descrita foi útil para nós - além de criar uma ferramenta que simplifica o processo de desenvolvimento, obtivemos um conhecimento verdadeiramente incondicional da linguagem. Uma caixa de Pandora levemente aberta com processos de baixo nível permite que você veja as tarefas diárias sob um novo ângulo.
Esperamos que o conhecimento adquirido também enriqueça sua compreensão do desenvolvimento!
O material foi co-escrito com
@victoriaqb - Victoria Kashlina, desenvolvedora do iOS.
Fontes
- Dispositivo compilador rápido. Parte 2
- Como criar uma ferramenta baseada no compilador Swift? O guia passo a passo
- Despejando o Swift AST para um projeto iOS
- Apresentando o testador de estresse do sourcekitd
- Teste rápido
- [SR-5744] Ação de refatoração para converter if-let em guard-let e vice-versa # 24566