Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
DiFuks committed Apr 27, 2024
1 parent 10d85dc commit 766ac63
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 173 deletions.
2 changes: 1 addition & 1 deletion packages/example/src/modern/getDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { someVar } from '../legacy/getDate';
export const getDate = (date) => {
const modern: string | undefined = undefined;

// Show string | undefined on hover in IDE
// Show `string | undefined` on hover in IDE
console.log(someVar);

modern.split('');
Expand Down
164 changes: 40 additions & 124 deletions packages/plugin/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,167 +1,83 @@
import outmatch from 'outmatch';
import * as path from 'path';
import type { PluginConfig, ProgramTransformer } from 'ts-patch';
import ts from 'typescript';

import { Override } from '../types/Override';

import {
getDiagnosticForFile,
getDiagnosticsForProject,
getOverridePrograms,
OverridePrograms,
} from './utils';

interface CliPluginConfig extends PluginConfig {
overrides: Override[];
}

const getOverridePrograms = (
rootPath: string,
typescript: typeof ts,
overridesFromConfig: Override[],
originalProgram: ts.Program,
host?: ts.CompilerHost
) => {
let filesToOriginalDiagnostic: string[] = [...originalProgram.getRootFileNames()];
const { plugins, ...defaultCompilerOptions } = originalProgram.getCompilerOptions();

const sortedOverrides = [...overridesFromConfig].reverse();

const resultOverrides: ts.Program[] = [];

for (const override of sortedOverrides) {
const isMatch = outmatch(override.files);
const filesToCurrentOverrideDiagnostic: string[] = [];

for (const fileName of filesToOriginalDiagnostic) {
const toOverrideDiagnostic = isMatch(path.relative(rootPath, fileName));

if (toOverrideDiagnostic) {
filesToCurrentOverrideDiagnostic.push(fileName);
}
}

const overrideProgram = typescript.createProgram(
filesToCurrentOverrideDiagnostic,
{
...defaultCompilerOptions,
...override.compilerOptions,
},
host
);
resultOverrides.push(overrideProgram);

filesToOriginalDiagnostic = filesToOriginalDiagnostic.filter(
(fileName) => !filesToCurrentOverrideDiagnostic.includes(fileName)
);
}

return { resultOverrides, filesToOriginalDiagnostic };
};

const getDiagnosticsForProject = (
overridePrograms: ts.Program[],
originalProgram: ts.Program,
filesToOriginalDiagnostic: string[],
cancellationToken?: ts.CancellationToken
): ts.Diagnostic[] => {
const diagnostics: ts.Diagnostic[] = [];
for (const overrideProgram of overridePrograms) {
for (const rootFileName of overrideProgram.getRootFileNames()) {
const sourceFile = overrideProgram.getSourceFile(rootFileName);

const diagnosticsForOverride = overrideProgram.getSemanticDiagnostics(
sourceFile,
cancellationToken
);

diagnostics.push(...diagnosticsForOverride);
}
}

for (const rootFileName of filesToOriginalDiagnostic) {
if (filesToOriginalDiagnostic.includes(rootFileName)) {
const sourceFile = originalProgram.getSourceFile(rootFileName);

const diagnosticsForOriginal = originalProgram.getSemanticDiagnostics(
sourceFile,
cancellationToken
);

diagnostics.push(...diagnosticsForOriginal);
}
}

return diagnostics;
};

let overridePrograms: {
filesToOriginalDiagnostic: string[];
resultOverrides: ts.Program[];
} | null = null;
let overridePrograms: OverridePrograms | null = null;

const plugin: ProgramTransformer = (program, host, pluginConfig, extras) => {
const { overrides: overridesFromConfig } = pluginConfig as CliPluginConfig;
const { plugins, ...defaultCompilerOptions } = program.getCompilerOptions();
const sortedOverridesFromConfig = [...overridesFromConfig].reverse();
const rootPath = defaultCompilerOptions.project
? path.dirname(defaultCompilerOptions.project)
: process.cwd();

overridePrograms = null;

overridePrograms = getOverridePrograms(rootPath, extras.ts, overridesFromConfig, program, host);
overridePrograms = getOverridePrograms(
rootPath,
extras.ts,
sortedOverridesFromConfig,
program.getRootFileNames(),
defaultCompilerOptions,
host
);

// Возвращать новую программу без файлов, которые подменяются оверрайдами
return new Proxy(program, {
get: (target, property: keyof ts.Program) => {
// for watch mode - ForkTsCheckerWebpackPlugin and tspc
if (property === 'getBindAndCheckDiagnostics') {
return ((sourceFile, cancellationToken) => {
const { fileName } = sourceFile;

if (!overridePrograms || overridePrograms.filesToOriginalDiagnostic.includes(fileName)) {
return target.getBindAndCheckDiagnostics(sourceFile, cancellationToken);
}

const overrideProgramForFile = overridePrograms?.resultOverrides.find(
(overrideProgram) => {
return overrideProgram.getRootFileNames().includes(fileName);
}
return getDiagnosticForFile(
overridePrograms,
target,
sourceFile,
'getBindAndCheckDiagnostics',
cancellationToken
);

return overrideProgramForFile
? overrideProgramForFile.getBindAndCheckDiagnostics(sourceFile, cancellationToken)
: target.getBindAndCheckDiagnostics(sourceFile, cancellationToken);
}) as ts.Program['getBindAndCheckDiagnostics'];
}

// for build mode
// for watch mode - ts-loader
if (property === 'getSemanticDiagnostics') {
return ((sourceFile, cancellationToken) => {
// for build ForkTsCheckerWebpackPlugin and tspc
if (!sourceFile) {
overridePrograms = null;

const overrideProgramsForBuild = getOverridePrograms(
return getDiagnosticsForProject(
rootPath,
extras.ts,
overridesFromConfig,
sortedOverridesFromConfig,
target,
defaultCompilerOptions,
cancellationToken,
host
);

return getDiagnosticsForProject(
overrideProgramsForBuild.resultOverrides,
target,
overrideProgramsForBuild.filesToOriginalDiagnostic,
cancellationToken
);
}

const { fileName } = sourceFile;

if (!overridePrograms || overridePrograms.filesToOriginalDiagnostic.includes(fileName)) {
return target.getSemanticDiagnostics(sourceFile, cancellationToken);
}

const overrideProgramForFile = overridePrograms?.resultOverrides.find(
(overrideProgram) => {
return overrideProgram.getRootFileNames().includes(fileName);
}
// for ts-loader - watch and build
return getDiagnosticForFile(
overridePrograms,
target,
sourceFile,
'getSemanticDiagnostics',
cancellationToken
);

return overrideProgramForFile
? overrideProgramForFile.getSemanticDiagnostics(sourceFile, cancellationToken)
: target.getSemanticDiagnostics(sourceFile, cancellationToken);
}) as ts.Program['getSemanticDiagnostics'];
}

Expand Down
136 changes: 136 additions & 0 deletions packages/plugin/src/cli/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import path from 'node:path';
import outmatch from 'outmatch';
import type ts from 'typescript';

import { Override } from '../types/Override';

const getOverrideProgram = (
rootPath: string,
typescript: typeof ts,
override: Override,
filesToOriginalDiagnostic: string[],
defaultCompilerOptions: ts.CompilerOptions,
host?: ts.CompilerHost
): {
overrideProgram: ts.Program;
filesToCurrentOverrideDiagnostic: string[];
} => {
const isMatch = outmatch(override.files);
const filesToCurrentOverrideDiagnostic: string[] = filesToOriginalDiagnostic.filter((fileName) =>
isMatch(path.relative(rootPath, fileName))
);

const overrideProgram = typescript.createProgram(
filesToCurrentOverrideDiagnostic,
{
...defaultCompilerOptions,
...override.compilerOptions,
},
host
);

return { overrideProgram, filesToCurrentOverrideDiagnostic };
};

export const getDiagnosticsForProject = (
rootPath: string,
typescript: typeof ts,
overridesFromConfig: Override[],
program: ts.Program,
defaultCompilerOptions: ts.CompilerOptions,
cancellationToken?: ts.CancellationToken,
host?: ts.CompilerHost
): ts.Diagnostic[] => {
let filesToOriginalDiagnostic: string[] = [...program.getRootFileNames()];

const resultDiagnostic: ts.Diagnostic[] = overridesFromConfig.flatMap((override) => {
const { overrideProgram, filesToCurrentOverrideDiagnostic } = getOverrideProgram(
rootPath,
typescript,
override,
filesToOriginalDiagnostic,
defaultCompilerOptions,
host
);

filesToOriginalDiagnostic = filesToOriginalDiagnostic.filter(
(fileName) => !filesToCurrentOverrideDiagnostic.includes(fileName)
);

return filesToCurrentOverrideDiagnostic.flatMap((fileName) => {
const sourceFile = overrideProgram.getSourceFile(fileName);

return sourceFile
? overrideProgram.getSemanticDiagnostics(sourceFile, cancellationToken)
: [];
});
});

const originalDiagnostics = filesToOriginalDiagnostic.flatMap((fileName) => {
const sourceFile = program.getSourceFile(fileName);

return sourceFile ? program.getSemanticDiagnostics(sourceFile, cancellationToken) : [];
});

return [...resultDiagnostic, ...originalDiagnostics];
};

export const getOverridePrograms = (
rootPath: string,
typescript: typeof ts,
overridesFromConfig: Override[],
rootFileNames: readonly string[],
defaultCompilerOptions: ts.CompilerOptions,
host?: ts.CompilerHost
): {
resultOverrides: ts.Program[];
filesToOriginalDiagnostic: string[];
} => {
let filesToOriginalDiagnostic: string[] = [...rootFileNames];

const resultOverrides: ts.Program[] = overridesFromConfig.map((override) => {
const { overrideProgram, filesToCurrentOverrideDiagnostic } = getOverrideProgram(
rootPath,
typescript,
override,
filesToOriginalDiagnostic,
defaultCompilerOptions,
host
);

filesToOriginalDiagnostic = filesToOriginalDiagnostic.filter(
(fileName) => !filesToCurrentOverrideDiagnostic.includes(fileName)
);

return overrideProgram;
});

return { resultOverrides, filesToOriginalDiagnostic };
};

export interface OverridePrograms {
filesToOriginalDiagnostic: string[];
resultOverrides: ts.Program[];
}

export const getDiagnosticForFile = (
overridePrograms: OverridePrograms | null,
target: ts.Program,
sourceFile: ts.SourceFile,
method: 'getSemanticDiagnostics' | 'getBindAndCheckDiagnostics',
cancellationToken?: ts.CancellationToken
): readonly ts.Diagnostic[] => {
const { fileName } = sourceFile;

if (!overridePrograms || overridePrograms.filesToOriginalDiagnostic.includes(fileName)) {
return target[method](sourceFile, cancellationToken);
}

const overrideProgramForFile = overridePrograms?.resultOverrides.find((overrideProgram) => {
return overrideProgram.getRootFileNames().includes(fileName);
});

return overrideProgramForFile
? overrideProgramForFile[method](sourceFile, cancellationToken)
: target[method](sourceFile, cancellationToken);
};
Loading

0 comments on commit 766ac63

Please sign in to comment.