Skip to content

Solution builder not detecting out of date compiler optionsΒ #62035

Open
@ernestostifano

Description

@ernestostifano

πŸ”Ž Search Terms

"solution builder cache", "tsbuildinfo cache", "compiler options change build"

πŸ•— Version & Regression Information

n/a

⏯ Playground Link

n/a

πŸ’» Code

n/a

πŸ™ Actual behavior

  1. In a project using references, suppose you have a tsconfig.json specifying any random option (e.g., skipLibCheck: true).
  2. Run a build using tsc --build ./tsconfig.json.
  3. See output.
  4. Run the build again, this time programmatically using createSolutionBuilder and override one compiler option via getParsedCommandLine in the builder host (e.g., skipLibCheck: false).
  5. Build will be skipped as the builder sees project files are not out of date, but fails to detect that compiler options are not the same used during the last build. We get the same output of the previous run.
  6. Cleaning up .tsbuildinfo files and running the build programmatically again work as expected. New output is different from the first one.

πŸ™‚ Expected behavior

I would expect the builder to detect that new compiler options are different from the last used and re-build that project.

Additional information about the issue

I narrowed down the issue to ./src/compiler/tsbuildPublic.ts - Line 1606

Basically, there is where UpToDateStatusType.OutOfDateOptions is reported, but current compiler options are not being compared with the ones from the relative tsbuildinfo file.

Manually changing the tsconfig.json file works because the timestamp of the file changes and not because options are different.

I discovered this while creating a script to address #30511

Script
const {
    formatDiagnostic,
    sys,
    createSolutionBuilderHost,
    getParsedCommandLineOfConfigFile,
    DiagnosticCategory,
    formatDiagnosticsWithColorAndContext,
    createSolutionBuilder
} = require('typescript');
const {resolveRoot} = require('../utils/resolve-root.cjs');

// PATH TO YOUR ROOT tsconfig.json (THE ONE WITH 'references')
const TSCONFIG_PATH = resolveRoot('./tsconfig.json');

// FUNCTION TO FORMAT DIAGNOSTICS (REUSED FOR CONSISTENCY)
const format = (diagnostic) => {
    return formatDiagnostic(diagnostic, {
        getCanonicalFileName: (fileName) => fileName,
        getCurrentDirectory: sys.getCurrentDirectory,
        getNewLine: () => sys.newLine
    });
};

const context = {
    allDiagnostics: [],
    workspaceDiagnostics: [],
    hasErrors: false,
    hasWorkspaceErrors: false
};

// CREATE THE DEFAULT SOLUTION BUILDER HOST FIRST
const defaultSolutionBuilderHost = createSolutionBuilderHost(sys);

// NOW, CREATE OUR CUSTOM HOST BY EXTENDING THE DEFAULT ONE
const customHost = {
    ...defaultSolutionBuilderHost,

    // OVERRIDE getParsedCommandLine TO MODIFY OPTIONS FOR EACH PROJECT
    // THIS FUNCTION IS CALLED BY THE SOLUTION BUILDER FOR EACH TSCONFIG FILE IT PROCESSES
    getParsedCommandLine: (fileName) => {
        const parsed = getParsedCommandLineOfConfigFile(
            fileName,
            {},
            {
                ...sys,
                onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
                    console.error(format(diagnostic));
                }
            }
        );

        if (parsed) {
            parsed.options.noEmitOnError = false;
            parsed.options.skipLibCheck = false;
        }

        return parsed;
    },

    // OVERRIDE reportDiagnostic TO ADD FILTERING LOGIC
    reportDiagnostic: (diagnostic) => {
        context.allDiagnostics.push(diagnostic);

        // FILTER DIAGNOSTICS: EXCLUDE THOSE FROM NODE_MODULES
        if (diagnostic.file) {
            const filePath = diagnostic.file.fileName;

            if (filePath.includes('node_modules')) {
                if (diagnostic.category === DiagnosticCategory.Error) {
                    context.hasErrors = true;
                }

                return;
            }
        }

        context.workspaceDiagnostics.push(diagnostic);

        if (diagnostic.category === DiagnosticCategory.Error) {
            context.hasErrors = true;
            context.hasWorkspaceErrors = true;
        }
    },

    // OVERRIDE reportSolutionBuilderStatus
    reportSolutionBuilderStatus: (diagnostic) => {
        // THESE ARE USUALLY BUILD STATUS MESSAGES, NOT CODE ERRORS.
        // YOU CAN CHOOSE TO FILTER THESE OR NOT. FOR NOW, LET'S PRINT THEM DIRECTLY.
        // YOU MIGHT STILL WANT TO USE formatDiagnostic FOR CONSISTENT OUTPUT
        console.log(format(diagnostic));
    }
};

// CREATE THE SOLUTION BUILDER USING YOUR CUSTOM HOST
const builder = createSolutionBuilder(customHost, [TSCONFIG_PATH], {
    verbose: true
});

// PERFORM THE BUILD
console.log();
console.log('STARTING SOLUTION BUILD...');
console.log();

builder.build();

console.log('SOLUTION BUILD FINISHED.');
console.log();

// AFTER THE BUILD, FORMAT AND PRINT THE COLLECTED (AND FILTERED) DIAGNOSTICS
if (context.workspaceDiagnostics.length) {
    console.log('WORKSPACE TYPESCRIPT DIAGNOSTICS:\n');

    context.workspaceDiagnostics.forEach((diag, index) => {
        const formatted = formatDiagnosticsWithColorAndContext([diag], {
            getCanonicalFileName: (fileName) => fileName,
            getCurrentDirectory: sys.getCurrentDirectory,
            getNewLine: () => sys.newLine
        });

        console.log(formatted);

        if (index < context.workspaceDiagnostics.length - 1) {
            console.log('\n---\n');
        }
    });
}

console.log(
    `WORKSPACE TYPESCRIPT DIAGNOSTICS FOUND: ${context.workspaceDiagnostics.length}`
);
console.log(
    `TOTAL TYPESCRIPT DIAGNOSTICS FOUND: ${context.allDiagnostics.length}`
);
console.log();

if (context.hasWorkspaceErrors) {
    throw new Error('BUILD CONTAINS ERRORS. SEE LOGS ABOVE.');
}

Note: I would be happy to open a PR

Metadata

Metadata

Assignees

Labels

Needs InvestigationThis issue needs a team member to investigate its status.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions