in packages/typescript/internal/ts_project_options_validator.ts [12:135]
function main([tsconfigPath, output, target, attrsStr]: string[]): 0|1 {
// The Bazel ts_project attributes were json-encoded
// (on Windows the quotes seem to be quoted wrong, so replace backslash with quotes :shrug:)
const attrs = JSON.parse(attrsStr.replace(/\\/g, '"'));
// Parse your typescript settings from the tsconfig
// This will understand the "extends" semantics.
const {config, error} = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
if (error) throw new Error(tsconfigPath + ':' + ts.formatDiagnostic(error, diagnosticsHost));
const {errors, options} =
ts.parseJsonConfigFileContent(config, ts.sys, require('path').dirname(tsconfigPath));
// We don't pass the srcs to this action, so it can't know if the program has the right sources.
// Diagnostics look like
// error TS18002: The 'files' list in config file 'tsconfig.json' is empty.
// error TS18003: No inputs were found in config file 'tsconfig.json'. Specified 'include'...
const fatalErrors = errors.filter(e => e.code !== 18002 && e.code != 18003);
if (fatalErrors.length > 0)
throw new Error(tsconfigPath + ':' + ts.formatDiagnostics(fatalErrors, diagnosticsHost));
const failures: string[] = [];
const buildozerCmds: string[] = [];
function getTsOption(option) {
if (typeof (options[option]) === 'string') {
// Currently the only string-typed options are filepaths.
// TypeScript will resolve these to a project path
// so when echoing that back to the user, we need to reverse that resolution.
// First turn //path/to/pkg:tsconfig into path/to/pkg
const packageDir = target.substr(2, target.indexOf(':') - 2);
return relative(packageDir, options[option] as string);
}
return options[option];
}
function check(option: string, attr?: string) {
attr = attr || option;
// treat compilerOptions undefined as false
const optionVal = getTsOption(option);
const match = optionVal === attrs[attr] ||
(optionVal === undefined && (attrs[attr] === false || attrs[attr] === ''));
if (!match) {
failures.push(
`attribute ${attr}=${attrs[attr]} does not match compilerOptions.${option}=${optionVal}`);
if (typeof (optionVal) === 'boolean') {
buildozerCmds.push(`set ${attr} ${optionVal ? 'True' : 'False'}`);
} else if (typeof (optionVal) === 'string') {
buildozerCmds.push(`set ${attr} \"${optionVal}\"`);
} else if (optionVal === undefined) {
// nothing to sync
} else {
throw new Error(`cannot check option ${option} of type ${typeof (option)}`);
}
}
}
const jsxEmit: Record<ts.JsxEmit, string|undefined> =
{
[ts.JsxEmit.None]: 'none',
[ts.JsxEmit.Preserve]: 'preserve',
[ts.JsxEmit.React]: 'react',
[ts.JsxEmit.ReactNative]: 'react-native',
[ts.JsxEmit.ReactJSX]: 'react-jsx',
[ts.JsxEmit.ReactJSXDev]: 'react-jsx-dev',
}
function check_preserve_jsx() {
const attr = 'preserve_jsx'
const jsxVal = options['jsx'] as ts.JsxEmit
if ((jsxVal === ts.JsxEmit.Preserve) !== Boolean(attrs[attr])) {
failures.push(
`attribute ${attr}=${attrs[attr]} does not match compilerOptions.jsx=${jsxEmit[jsxVal]}`);
buildozerCmds.push(`set ${attr} ${jsxVal === ts.JsxEmit.Preserve ? 'True' : 'False'}`);
}
}
if (options.noEmit) {
console.error(`ERROR: ts_project rule ${
target} cannot be built because the 'noEmit' option is specified in the tsconfig.`);
console.error('This is not compatible with ts_project, which always produces outputs.');
console.error('- If you mean to only typecheck the code, use the tsc_test rule instead.');
console.error(' (See the Alternatives section in the documentation.)');
console.error('- Otherwise, remove the noEmit option from tsconfig and try again.');
return 1;
}
check('allowJs', 'allow_js');
check('declarationMap', 'declaration_map');
check('emitDeclarationOnly', 'emit_declaration_only');
check('resolveJsonModule', 'resolve_json_module');
check('sourceMap', 'source_map');
check('composite');
check('declaration');
check('incremental');
check('tsBuildInfoFile', 'ts_build_info_file');
check_preserve_jsx();
if (failures.length > 0) {
console.error(`ERROR: ts_project rule ${
target} was configured with attributes that don't match the tsconfig`);
failures.forEach(f => console.error(' - ' + f));
console.error('You can automatically fix this by running:');
console.error(
` npx @bazel/buildozer ${buildozerCmds.map(c => `'${c}'`).join(' ')} ${target}`);
return 1;
}
// We have to write an output so that Bazel needs to execute this action.
// Make the output change whenever the attributes changed.
require('fs').writeFileSync(
output, `
// ${process.argv[1]} checked attributes for ${target}
// allow_js: ${attrs.allow_js}
// composite: ${attrs.composite}
// declaration: ${attrs.declaration}
// declaration_map: ${attrs.declaration_map}
// incremental: ${attrs.incremental}
// source_map: ${attrs.source_map}
// emit_declaration_only: ${attrs.emit_declaration_only}
// ts_build_info_file: ${attrs.ts_build_info_file}
// preserve_jsx: ${attrs.preserve_jsx}
`,
'utf-8');
return 0;
}