Consider the following scenario: TypeScript fails to recognize that parameters.a
and parameters.b
have been checked for undefined values, leading to a potential issue where transformValue(parameters.a)
line might return an undefined value:
type Scenario = {
a?: string,
b?: string
}
function scenario(parameters: {
a?: string,
b?: string,
skipA?: boolean,
skipB?: boolean
}): Scenario {
const shouldReturnA = typeof parameters.a !== "undefined" && parameters.skipA !== false;
const shouldReturnB = typeof parameters.b !== "undefined" && parameters.skipB !== false;
return {
...shouldReturnA ? { a: transformValue(parameters.a) } : {},
...shouldReturnB ? { b: transformValue(parameters.b) } : {}
}
}
function transformValue(targetValue: string): string {
return targetValue + "~~~";
}
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.(2345)
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.(2345)
To simplify this synthetic example, it can be rewritten as follows:
function scenario(parameters: {
a?: string,
b?: string,
skipA?: boolean,
skipB?: boolean
}): Scenario {
return {
...typeof parameters.a !== "undefined" && parameters.skipA !== false ? {
a: transformValue(parameters.a)
} : {},
...shouldReturnB = typeof parameters.b !== "undefined" && parameters.skipB !== false ? {
b: transformValue(parameters.b)
} : {}
}
}
However, when looking at a live example like the one below:
public static normalizeRawConfig(
{
pickedFromConsoleInputConfig,
rawValidConfigFromFile
}: {
pickedFromConsoleInputConfig: ProjectBuilderRawConfigNormalizer.PickedFromConsoleInputConfig;
rawValidConfigFromFile: ProjectBuilderRawValidConfigFromFile;
}
): ProjectBuilderNormalizedConfig {
const markupPreprocessingConfigNormalizingIsRequired: boolean =
!isUndefined(rawValidConfigFromFile[ProjectBuilderTasksIDsForConfigFile.markupPreprocessing]) &&
(
isUndefined(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection) ||
Object.keys(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection)
.includes(ProjectBuilderTasksIDsForConfigFile.markupPreprocessing)
);
const stylesPreprocessingConfigNormalizingIsRequired: boolean =
!isUndefined(rawValidConfigFromFile[ProjectBuilderTasksIDsForConfigFile.stylesPreprocessing]) &&
(
isUndefined(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection) ||
Object.keys(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection)
.includes(ProjectBuilderTasksIDsForConfigFile.stylesPreprocessing)
);
return {
...markupPreprocessingConfigNormalizingIsRequired ? {
markupPreprocessing: MarkupPreprocessingRawSettingsNormalizer.getNormalizedSettings(
rawValidConfigFromFile.markupPreprocessing, commonSettings__normalized
)
} : {},
...stylesPreprocessingConfigNormalizingIsRequired ? {} : {
stylesPreprocessing: StylesPreprocessingRawSettingsNormalizer.getNormalizedSettings(
rawValidConfigFromFile.stylesPreprocessing, commonSettings__normalized
)
}
};
}
If we remove
markupPreprocessingConfigNormalizingIsRequired
and stylesPreprocessingConfigNormalizingIsRequired
, the code becomes more complex:
return {
...!isUndefined(rawValidConfigFromFile[ProjectBuilderTasksIDsForConfigFile.markupPreprocessing]) &&
(
isUndefined(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection) ||
Object.keys(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection)
.includes(ProjectBuilderTasksIDsForConfigFile.markupPreprocessing)
) ? {
markupPreprocessing: MarkupPreprocessingRawSettingsNormalizer.getNormalizedSettings(
rawValidConfigFromFile.markupPreprocessing, commonSettings__normalized
)
} : {},
...!isUndefined(rawValidConfigFromFile[ProjectBuilderTasksIDsForConfigFile.stylesPreprocessing]) &&
(
isUndefined(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection) ||
Object.keys(pickedFromConsoleInputConfig.tasksAndSourceFilesSelection)
.includes(ProjectBuilderTasksIDsForConfigFile.stylesPreprocessing)
) ? {
stylesPreprocessing: StylesPreprocessingRawSettingsNormalizer.getNormalizedSettings(
rawValidConfigFromFile.stylesPreprocessing, commonSettings__normalized
)
} : {}
};
This approach can lead to more complicated conditions in the future.
Are there any best practices in dealing with such scenarios in TypeScript?
Update
After trying to compare XXX !== undefined
instead of typeof XXX !== "undefined"
, similar results were obtained.