Tslint: Proposta: configuração condicional de lint

Criado em 3 nov. 2017  ·  42Comentários  ·  Fonte: palantir/tslint

Existem algumas situações em que gostaríamos de impor condicionalmente uma verificação de lint com base no fato de o código que está sendo lintado ser um código de teste ou não. Por exemplo, gostaríamos de banir 'qualquer' do código de produção, uma vez que não é seguro. Mas em testes, 'any' pode ser útil para fornecer implementações falsas ou simuladas de serviços, ou para lançar através dele para que você possa chamar métodos privados. Ou podemos querer banir um conjunto diferente de métodos em testes e no código de produção, por exemplo.

O que proponho é uma maneira de habilitar condicionalmente as verificações de lint com base em uma correspondência de regex no nome do arquivo. Isso também pode ser útil para desativar verificações de lint para determinados diretórios (contendo código "legado", por exemplo) ou para arquivos .tsx ou .d.ts etc.

Aqui estão algumas ideias de espantalho de sintaxe; Também estou aberto a sugestões.

Objeto de seções

{
  "rules": {
    "no-any": {"other": true},
    "no-console":
        {
          "test": [true, "warn", "error"], 
          "other": [true, "log", "warn", "error"]
        }
  },
  "sections": {
    "test": ".*\\.spec\\.ts$"
  }
}

"Outros" seriam coisas que não correspondem a nenhuma das expressões regulares. Isso é muito conciso, mas acho que a legibilidade da seção de regras sofre um pouco.

Regras de "substituição"

{
  "rules": {
    "no-any": true,
    "no-console": [true, "log", "warn", "error"]
  },
  "override": {
    "match": ".*\\.spec\\.ts$",
    "rules": {
      "no-any": false,
      "no-console": [true, "warn", "error"]
    }
  }
}

(isso talvez deva ser um array, para permitir várias substituições também).

Declined Enhancement

Comentários muito úteis

O aninhamento não resolve os problemas discutidos aqui. Muitas pessoas querem que seus testes na mesma estrutura de diretório como o código que testa, e não há simplesmente nenhuma boa maneira de fazer isso com tslint agora.

Todos 42 comentários

Se você colocar seus testes e código de produção em diretórios separados, não precisará de tal recurso. Se você não fornecer um arquivo de configuração como argumento CLI, o TSLint usará o tslint.json mais próximo. Portanto, você pode ter configurações diferentes para pastas diferentes. Usando "extends" você pode usar a mesma configuração de base e substituir apenas regras específicas.

Eu recebo tantos projetos Angular e React que colocam testes no mesmo diretório que o código de produção correspondente. Pessoalmente, acho que essa é uma prática ruim. Já vi pessoas suficientes (acidentalmente) importando símbolos de testes em código de produção.

IMO, o recurso proposto adiciona muita complexidade. A implementação pode não ser tão difícil, mas torna o arquivo de configuração difícil de ler e entender.
Se vamos implementar isso, prefiro a segunda sugestão.

Também precisaríamos discutir como funciona ao estender as configurações. Em que ordem as substituições são aplicadas?

É verdade que você poderia apenas separar o código, mas isso prejudica a ergonomia. Ou você tem

src/
  tslint.json
  a/
    b/
      c/
test/
  tslint.json (extends ../src)
  a/
    b/
      c/

e c/foo.spec.ts tem que import {symbol} from '../../../a/b/c/foo';

Esse caminho fica feio em nosso monorepo, onde os arquivos costumam estar muito profundos na árvore.

ou você tem

  a/
    src/
      tslint.json
    test/
      tslint.json (extends ../src)
    b/
      src/
        tslint.json
      test/
        tslint.json (extends ../src)
      c/
        src/
           tslint.json
        test/
          tslint.json (extends ../src)

que é muitos arquivos tslint.json

Sobre o tópico de substituições: eu diria que as substituições se aplicam na mesma ordem em que as regras são aplicadas. Não sei o que é e não consegui encontrar a documentação, mas intuitivamente acho que seria assim:

// a.json
{"rules": {"foo": [true, 1]}}

// b.json
{"rules": {"foo": [true, 2]}}

// c.json
{
  "extends": ["a.json", "b.json"],
  "rules": {"foo": [true, 3]}
}

resultaria na regra "foo" com o argumento 3. Se essa regra não estivesse em c.json , então "foo" deveria ter o valor 2, por causa da ordem do array "extends".

Para substituições, esperaria que o mesmo pedido fosse aplicado. Para mim, faz sentido usar a última substituição que corresponde à regex fornecida, levando em consideração a ordem em que as regras de 'estende' são consideradas. Um único exemplo de arquivo:

{
  "rules": {"a": [true, 1]},
  "override": [
    {"match": "test|spec", "rules": {"a": [true, 2]}},
    {"match": "test", "rules": {"a": [true, 3]}}
  ]
}

teria valor 2 para a regra "foo" quando o caminho do arquivo contiver "spec", 3 se contiver "test" e 1 caso contrário. (obviamente, não é um conjunto de substituições muito útil, já que "teste" também pode ser omitido do primeiro, mas você entendeu)

Isso faz sentido e responde à sua pergunta?

@calebegg Existem alguns outros casos

// a.json
{
  "rules": {"foo": [true, 1]},
  "override": {"match": "test|spec", "rules": {"foo": [true, 2]}}
}

// b.json
{
  "extends": "./a.json"
  "rules": {"foo": false}
}

Qual é o resultado esperado?

  • 2 porque o pedido é a.json/rules -> b.json/rules -> a.json/override
  • false porque o pedido é a.json/rules -> a.json/override -> b.json/rules

Mesmo aqui, qual é o resultado esperado:

// c.json
{
  "rules": {"foo": [true, 1]},
  "override": {"match": "test|spec", "rules": {"foo": false}}
}

// d.json
{
  "extends": "./a.json"
  "rules": {"foo": {"options": 2}},
  "override": {"match": "test|spec", "rules": {"foo": {"options": 3}}}
}

Apenas um pensamento aleatório: por que não apenas combinar os padrões em ordem e (parcialmente) substituir a correspondência anterior:

{
  "rules": { // same as "*"
    "rule-one": true,
    "rule-two": true
  },
  "*.js?(x)": { // we could get rid of "jsRules" with this
    "rule-two": false
  },
  "*.{t,j}sx": {
    "jsx-rule": true
  },
  "*.spec.*" {
    "rule-one": false
  }
}

Alguns exemplos:

  • foo.ts : regra um, regra dois
  • foo.tsx : regra-um, regra-dois, jsx-regra
  • foo.js : regra um
  • foo.jsx : regra-um, jsx-regra
  • foo.spec.tsx : regra-dois, jsx-regra
  • foo.spec.js : nenhum

Só não sei onde colocar isso na configuração. O mesmo nível que "rules" torna mais fácil escrever, mas pode confundir os usuários, porque está no mesmo nível que "extends" , "rulesDirectory" , ...

Outro problema é estender as configurações. Se aplicarmos todas as substituições da configuração de base e, em seguida, as substituições da configuração atual, é um pouco difícil de entender.

Também torna o código mais complexo. Atualmente, as configurações são mescladas durante a análise. Com esta proposta, a configuração final só é conhecida quando temos um nome de arquivo.

Olá,

Esse recurso será ótimo. Acho que Eslint está oferecendo suporte a esse recurso: https://eslint.org/docs/user-guide/configuring#configuration -based-on-glob-patterns. Portanto, sintaxe e semântica podem ser iguais, para evitar confusão.

@minomikula Muito obrigado. Eu pesquisei os documentos ESLint antes, mas não encontrei esta seção.

A abordagem deles faz sentido. Para resumir:

  • "overrides" seção
  • uma substituição pode especificar vários padrões glob ( "files" ). se um deles corresponder, a configuração se aplica
  • você pode excluir arquivos pelo padrão glob: "excludedFiles"
  • padrões glob são relativos ao arquivo de configuração em que são especificados
  • padrões glob sempre precisam corresponder a todo o caminho, não apenas o nome de base
  • as substituições são processadas em ordem, substituindo a anterior
  • ao estender um arquivo de configuração, a configuração de base é completamente avaliada antes de continuar para a configuração de extensão

    • base.json / rules

    • base.json / overrides

    • extending.json / rules

    • extending.json / overrides

Há algumas coisas a serem consideradas ao portar esse comportamento para o TSLint:

  • devemos definitivamente desativar extends e linterOptions nas substituições
  • provavelmente também queremos proibir rulesDirectory
  • queremos permitir rules e jsRules em substituições ou desaprovamos jsRules em favor de uma substituição de **/*.js?(x) ?

Pensamentos ou comentários @adidahiya @calebegg @alexeagle @minomikula?

Oh, uau, sim, essa abordagem parece razoável para mim. Eu também deveria ter procurado arte anterior.

Esta seria uma adição incrível! A abordagem eslint funciona muito bem ao desabilitar condicionalmente algumas regras em arquivos de teste.

@calebegg , você ainda planeja levar isso adiante quando tiver tempo?

Sim, definitivamente gostaria de trabalhar nisso. @ajafff ​​Em geral, você está satisfeito em começar com a abordagem ESLint e iterar nos PRs? Ou você acha que há questões de design sobre as quais devemos falar primeiro?

queremos permitir regras e jsRules em substituições ou desaprovamos jsRules em favor de uma substituição * / .js? (x)?

Para mim, faz sentido depreciar, mas não me sinto muito convicto de qualquer maneira.

@calebegg Estou bem com a abordagem ESLint. Implementei um conceito muito semelhante em meu tempo de execução POC linter https://github.com/fimbullinter/wotan/tree/master/packages/wotan#overrides

A integração na API TSLint atual pode ser difícil, porque atualmente ela mescla todas as configurações durante a análise. Isso precisa ser adiado até que o nome do arquivo seja conhecido e precisa ser feito para cada arquivo.

Esta é certamente uma alteração importante da API.

@calebegg @mitchlloyd @ajafff @alexeagle vejo poucos benefícios neste recurso em relação ao aninhamento de arquivos tslint.json para substituir as configurações externas. esse recurso já funciona hoje e não requer quebras complexas de API. você ficaria ofendido se rejeitássemos essa solicitação?

O aninhamento não resolve os problemas discutidos aqui. Muitas pessoas querem que seus testes na mesma estrutura de diretório como o código que testa, e não há simplesmente nenhuma boa maneira de fazer isso com tslint agora.

Existem muitos projetos colocando arquivos de especificações no mesmo diretório que os fontes. Isso se tornou uma prática recomendada em muitos ambientes, o Angular CLI gera uma estrutura de projeto como essa, etc. Não acho que seja realista aninhar arquivos de configuração tslint vai funcionar para todas essas pessoas.

Concordo que a estrutura do meu projeto não deve ser ditada por uma ferramenta de linting. Em vez disso, a ferramenta de linting deve oferecer suporte a padrões comuns existentes. Colocar testes e arquivos de origem é um padrão comum.

@giladgray A mudança solicitada oferece suporte a um cenário em que os usuários colocam seus testes e arquivos de produção próximos uns dos outros, como este:

some-dir/
  my-component.ts
  my-component.test.ts

Como o "aninhamento de tslint.json arquivos" resolveria esse caso de uso quando queremos regras diferentes para my-component.ts e my-component.test.ts ?

As diretrizes angulares do

@ohjames Vamos nos manter no tópico e evitar desviar para ataques pessoais, ok? Sugerir que alguém não está apto para manter seu próprio projeto não é a maneira de influenciar o OSS e certamente não vai ajudar no que todos estamos defendendo aqui.

Faz sentido desejar substituições para arquivos que não se encaixam em uma estrutura aninhada, conforme mencionado acima. Parece-me que o comentário de @giladgray tinha a intenção de determinar se ainda havia um interesse significativo da comunidade nesta mudança, visto que não havia nenhuma atividade sobre o assunto por mais de 6 meses. Isso é evidenciado especificamente por seu comentário sobre o PR aberto (# 3708):

se você deseja prosseguir com isso, atualize este branch para que possamos analisá-lo ou feche-o se não for mais relevante. fecharemos se não tivermos notícias suas em duas semanas.

@ohjames , sugiro que você reconsidere seu comentário - como @DanielSchaffer mencionou, ataques pessoais não vão ajudá-lo aqui, ou em qualquer lugar no software de código aberto. Todos estão dando o melhor de si para criar softwares bons, confiáveis ​​e ricos em recursos, e atacar pessoas de forma alguma contribui para isso.

Existe alguma atualização neste tópico?
A ideia proposta por @ajafff em seu comentário resumido parece razoável e satisfaz toda a variedade de necessidades. Usar a mesma semântica do eslint é um bom caminho a seguir.

Como comentei no # 1063 relacionado, meu caso de uso seria o de que o uso de tslint-microsoft-contrib atualmente bombardeia em componentes de arquivo único Vue para algumas regras. Isso pode ser um problema do Vue ou do TSLint, e essas equipes podem ou não resolver o problema específico - desabilitar as regras ofensivas até que isso aconteça parece uma solução simples que não é muito complicada. Comparado a ter que adicionar a mesma diretiva de desativação a cada arquivo .vue e atualizá-lo em todos os lugares conforme o suporte à regra muda. Também não faz sentido dividir um projeto em arquivos .vue e .ts, seria uma estrutura muito estranha.

Eu sou a favor desse recurso. Talvez possamos também resolver a solicitação tslintignore em # 73 com ele, permitindo que algumas substituições desabilitem completamente todas as regras?

{
    "files": "./src/test-data/*.ts",
    "reset": true // Resets tslint.json to {}
}

tslint-microsoft-contrib atualmente ataca em componentes de arquivo único do Vue para algumas regras

Oh, 😕 @millimoose, você poderia registrar um problema em tslint-microsoft-contrib?

@JoshuaKGoldberg - Já havia feito um relatório detalhado desse bug em @ vuejs / vue-cli que parecia o mais apropriado. (Está marcado como "aguardando reprodução", então suponho que não esteja totalmente fora do lugar.) Acabei de mencioná-lo como um motivador para concordar aqui. É o tipo de coisa que pode e vai surgir em um ecossistema em evolução, o que deveria ser um argumento para ferramentas serem flexíveis o suficiente para contornar obstáculos sem realmente escrever hacks para os bugs dos outros.

Alguma atualização sobre este problema?

Este problema está sinalizado como 'Requer proposta', mas acho que já temos uma proposta em https://github.com/palantir/tslint/issues/3447#issuecomment -344020834?

A proposta de @RoystonS @ajafff mencionou algumas pontas soltas que devem ser resolvidas:

Há algumas coisas a serem consideradas ao portar esse comportamento para o TSLint:

  • devemos definitivamente desativar extends e linterOptions nas substituições
  • provavelmente também queremos proibir rulesDirectory
  • queremos permitir rules e jsRules em substituições ou desaprovamos jsRules em favor de uma substituição de **/*.js?(x) ?

Também deve haver um consenso do pessoal da palantirtech org sobre isso antes de avançar, pois é um recurso bastante substancial.

Acabei de descobrir isso sozinho e só quero expressar meu interesse por esse tipo de recurso.

Acabamos relaxando algumas regras de linting que afetam o código de produção e vamos ver como funciona. ):

Eu adoraria ver esse recurso

assinando - também preciso disso.

Eu preciso tanto disso :(

Mesmo aqui! Estamos usando o typescript com Vuejs e nossos arquivos de teste de unidade não estão juntos em um diretório de testes, mas em todos os lugares ao longo do código-fonte do componente.

Gostaríamos muito de ver esse recurso. No momento, estamos usando uma configuração um pouco instável que resulta no vscode destacando erros de linting diferentes do que nosso lint de linha de comando destaca para contornar esse problema.

Eu duvido muito que esse recurso seja implementado, o tslint não parece ter uma manutenção ativa em geral. Eu sugiro mudar para @typescript-eslint , ele tem suporte total dos projetos eslint e ts e a migração é mais fácil do que nunca com @ typescript-eslint / eslint-plugin-tslint . E há uma tabela de compatibilidade com tslint e regras eslint correspondentes / outras alternativas disponíveis. A configuração do Eslint é altamente personalizável, então não vejo motivo para continuar com o tslint.

Proponho aplicar o conceito de repositório git usado para arquivos no .gitIgnore ... às regras tsLint. Uso de sua posição hierárquica atual como contexto para o linter, junto com um arquivo de substituição, ao lintar um subdiretório específico. Em seguida, você pode incluir ou excluir, configurar qualquer configuração em qualquer nível de diretório que desejar.

por exemplo
Uma configuração tslint é colocada na raiz e, em qualquer nível particular da estrutura do código-fonte, você gostaria de um comportamento diferente, simplesmente substitua-o por um arquivo tslint de extensão na raiz desse subdiretório.

@redevill Ajuda as pessoas a escreverem testes em uma pasta test . Mas não um teste de escrita como something.test.ts na mesma pasta do componente / serviço.

Concordo - mas com pequenas alterações: (que pode ser automatizado com o gerador de modelo (estilo CLI)
MyComponentDir
--- MyComponentTestsDir
------ tslint.test.json
------ alguma coisa.teste.ts
--- mycomponent.component.css
--- mycomponent.component.html
--- mycomponent.component.ts

A ideia ainda pode funcionar.

Grande fã dessa proposta. SIM POR FAVOR! Minha preferência seria o conceito Overrides.

A propósito, dado o anúncio de descontinuação de tslint, eu não posso _realmente_ imaginar que uma mudança tão grande será feita neste estágio, e minha equipe está mudando para eslint, que já tem recursos de substituição por regra + por arquivo.

@RoystonS está basicamente certo, um recurso tão grande não será adicionado ao tslint neste momento. ver # 4534

Usamos um projeto Angular. Precisamos do conceito de substituições. Caso contrário, é difícil gerenciar

Esta página foi útil?
0 / 5 - 0 avaliações