Typescript derleyici eklentisi/dönüştürücüsü çalıştırıldıktan sonra tip denetimi


Bir Typescript derleyici eklentisi/dönüştürücüsü nasıl yazılır konusunu anlatan bir blogu (https://dev.doctorevidence.com/how-to-write-a-typescript-transform-plugin-fc5308fdd943) takip ediyorum.

Bir nesnede olmayan bir özelliğe erişilen bir tip hatası oluşturması gereken ilk basit dönüşümü uyguladıktan sonra, herhangi bir tip hatası gösterilmediğini fark ettim. Aslında, derleyici normal şekilde devam ediyor.

import * as ts from "typescript";

export const transformerFactory = (
  program: ts.Program
): ts.TransformerFactory<ts.SourceFile> => {
  return (context: ts.TransformationContext): ts.Transformer<ts.SourceFile> => {
    const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<ts.Node> => {
      if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
        if (node.expression.escapedText === "someCall") {
          return ts.createCall(
            ts.createPropertyAccess(node.expression, "nonExisting"),
            node.typeArguments,
            node.arguments
          );
        }
      }
      return ts.visitEachChild(node, visitor, context);
    };

    return (sf: ts.SourceFile) => ts.visitNode(sf, visitor);
  };
};

index.ts dosyasına uygulandı:

declare function someCall(...args: any[]): string;

console.log(someCall(1, 2, true));

index.js verir:

console.log(someCall.nonExisting(1, 2, true));

(hatadaYayınlamaYok: true olsa bile)

Bu amaçlanan bir davranış mı? Bunu bir yerden etkinleştirebilir miyim?

Cevap #1

Bu kasıtlı bir davranış mı?

Evet.

Bunu bir yerden etkinleştirebilir miyim?

Hayır, dönüştürücülerin sınırlı bir amacı vardır. Derleyici için genel amaçlı tüm "eklenti"ler desteklenmemektedir.

Transformers, tip kontrollü AST'den JavaScript kodu üreten "emit" aşamasının bir parçası olarak çalıştırılır.

Transformers PR'sindeki bu yorum şöyle diyor

Tüm dönüşümler, kontrol aşaması gerçekleştikten sonra olur

GÜNCELLEME

Dosyayı dönüştürmek için bir kez ve tümünü tür denetimi yapmak için bir kez olmak üzere iki kez derlemenin bir yolu var mı? Dönüştürülmüş dosyalar için ayrı bir kontrol çalıştırmam gerekirse sorun değil.

Bilmiyorum. İlk denenecek şey, transformatörlerinizin AST'yi eskisi gibi değiştirmesi, ardından değiştirilmiş dosyaları manuel olarak çağırarak tip kontrolü yapmaktır

program.getDiagnosticsProducingTypeChecker().getDiagnostics(sourceFile)

(getDiagnostics ikinci bir parametreye sahip - cancellationToken - ama her zaman tür denetleyicisi kodunda undefined olup olmadığı kontrol edildiği için onu belirtmemek güvenli görünüyor. Genel olarak, çeşitli derleyici API'lerinin kendi kaynak kodunda nasıl kullanıldığını inceleyebilirsiniz, örneğin emit önce çeşitli program.getNNNDiagnostics çağrılarıyla tür denetimi yapar, ardından dönüştürmelerle birlikte emitter çalıştırır.)

Bu işe yarayabilir ya da yaramayabilir, çünkü tür denetleyicisi AST'yi değiştirir ve bu, AST'nin doğru durumda olmasına bağlıdır.

O zaman, builder API'sine bakmak isteyebilirsiniz - amacı, kaynak dosya değişikliklerini izlemek ve değişen dosyaları yeniden derlemektir (kaynak kodu bağlantısı). AST değişikliklerinde yeniden derleme yapmak ne kadar zor olur bilmiyorum, ayrıca tranformers'da mevcut olan ziyaretçileri kullanamayacak gibi görünüyorsunuz; AST'yi elle gezmeniz gerekecek.

Ayrıca, amacı 'TypeScript ve JavaScript kodlarını gezinmek ve değiştirmek için basit bir yol sağlamak' olarak belirtilen ts-simple-ast kütüphanesi de var. Ben kendim kullanmadım ve hedefiniz için ne kadar faydalı olduğunu bilmiyorum.

Cevap #2

Sadece bir CompilerHost oluşturmanız ve getSourceFile metodunu, dönüşüm sonrası kaynak dosyalarınıza yönlendirecek şekilde ayarlamanız yeterlidir. Bunu yapmanın bir yolu, dosya adlarından dönüştürülmüş kaynak dosyalara bir Map sahip olmaktır. Daha sonra CompilerHost oluşturma işlemi şu şekilde görünecektir:

const compilerHost = ts.createCompilerHost(compilerOptions);
    const defaultLibFileName = ts.getDefaultLibFileName(compilerOptions);
    compilerHost.getSourceFile = (sourceName) => {
      let sourcePath = sourceName;
      if (sourceName === defaultLibFileName) {
        sourcePath = ts.getDefaultLibFilePath(compilerOptions);
      } else if (this.sourceFileMap.has(sourceName)) {
        return this.sourceFileMap.get(sourceName);
      }
      if (!fs.existsSync(sourcePath)) {
        return undefined;
      }
      const contents = fs.readFileSync(sourcePath, 'utf-8');
      return ts.createSourceFile(sourceName, contents, COMPILER_OPTIONS.target);
    };

O zaman bu CompilerHost'u ts.createProgram()'a üçüncü argüman olarak geçirmeniz yeterlidir

İlgili makaleler