This commit upgrades TypeScript from 5.4 to 5.5 and enables the
`noImplicitAny` option for stricter type checking. It refactors code to
comply with `noImplicitAny` and adapts to new TypeScript features and
limitations.
Key changes:
- Migrate from TypeScript 5.4 to 5.5
- Enable `noImplicitAny` for stricter type checking
- Refactor code to comply with new TypeScript features and limitations
Other supporting changes:
- Refactor progress bar handling for type safety
- Drop 'I' prefix from interfaces to align with new code convention
- Update TypeScript target from `ES2017` and `ES2018`.
This allows named capturing groups. Otherwise, new TypeScript compiler
does not compile the project and shows the following error:
```
...
TimestampedFilenameGenerator.spec.ts:105:23 - error TS1503: Named capturing groups are only available when targeting 'ES2018' or later
const pattern = /^(?<timestamp>\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-(?<scriptName>[^.]+?)(?:\.(?<extension>[^.]+))?$/;// timestamp-scriptName.extension
...
```
- Refactor usage of `electron-progressbar` for type safety and
less complexity.
148 lines
5.2 KiB
TypeScript
148 lines
5.2 KiB
TypeScript
import type { ScriptData, CodeScriptData, CallScriptData } from '@/application/collections/';
|
|
import { RecommendationLevel } from '@/domain/Executables/Script/RecommendationLevel';
|
|
import type { ScriptCode } from '@/domain/Executables/Script/Code/ScriptCode';
|
|
import { validateCode, type CodeValidator } from '@/application/Parser/Executable/Script/Validation/CodeValidator';
|
|
import { wrapErrorWithAdditionalContext, type ErrorWithContextWrapper } from '@/application/Parser/Common/ContextualError';
|
|
import type { ScriptCodeFactory } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
|
import { createScriptCode } from '@/domain/Executables/Script/Code/ScriptCodeFactory';
|
|
import type { Script } from '@/domain/Executables/Script/Script';
|
|
import { createEnumParser, type EnumParser } from '@/application/Common/Enum';
|
|
import { filterEmptyStrings } from '@/application/Common/Text/FilterEmptyStrings';
|
|
import { createScript, type ScriptFactory } from '@/domain/Executables/Script/ScriptFactory';
|
|
import type { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
|
import { CodeValidationRule } from '@/application/Parser/Executable/Script/Validation/CodeValidationRule';
|
|
import { parseDocs, type DocsParser } from '../DocumentationParser';
|
|
import { ExecutableType } from '../Validation/ExecutableType';
|
|
import { createExecutableDataValidator, type ExecutableValidator, type ExecutableValidatorFactory } from '../Validation/ExecutableValidator';
|
|
import type { CategoryCollectionContext } from '../CategoryCollectionContext';
|
|
|
|
export interface ScriptParser {
|
|
(
|
|
data: ScriptData,
|
|
collectionContext: CategoryCollectionContext,
|
|
scriptUtilities?: ScriptParserUtilities,
|
|
): Script;
|
|
}
|
|
|
|
export const parseScript: ScriptParser = (
|
|
data,
|
|
collectionContext,
|
|
scriptUtilities = DefaultUtilities,
|
|
) => {
|
|
const validator = scriptUtilities.createValidator({
|
|
type: ExecutableType.Script,
|
|
self: data,
|
|
});
|
|
validateScript(data, validator);
|
|
try {
|
|
const script = scriptUtilities.createScript({
|
|
executableId: data.name, // Pseudo-ID for uniqueness until real ID support
|
|
name: data.name,
|
|
code: parseCode(
|
|
data,
|
|
collectionContext,
|
|
scriptUtilities.codeValidator,
|
|
scriptUtilities.createCode,
|
|
),
|
|
docs: scriptUtilities.parseDocs(data),
|
|
level: parseLevel(data.recommend, scriptUtilities.levelParser),
|
|
});
|
|
return script;
|
|
} catch (error) {
|
|
throw scriptUtilities.wrapError(
|
|
error,
|
|
validator.createContextualErrorMessage('Failed to parse script.'),
|
|
);
|
|
}
|
|
};
|
|
|
|
function parseLevel(
|
|
level: string | undefined,
|
|
parser: EnumParser<RecommendationLevel>,
|
|
): RecommendationLevel | undefined {
|
|
if (!level) {
|
|
return undefined;
|
|
}
|
|
return parser.parseEnum(level, 'level');
|
|
}
|
|
|
|
function parseCode(
|
|
script: ScriptData,
|
|
collectionContext: CategoryCollectionContext,
|
|
codeValidator: CodeValidator,
|
|
createCode: ScriptCodeFactory,
|
|
): ScriptCode {
|
|
if (collectionContext.compiler.canCompile(script)) {
|
|
return collectionContext.compiler.compile(script);
|
|
}
|
|
const codeScript = script as CodeScriptData; // Must be inline code if it cannot be compiled
|
|
const code = createCode(codeScript.code, codeScript.revertCode);
|
|
validateHardcodedCodeWithoutCalls(code, codeValidator, collectionContext.language);
|
|
return code;
|
|
}
|
|
|
|
function validateHardcodedCodeWithoutCalls(
|
|
scriptCode: ScriptCode,
|
|
validate: CodeValidator,
|
|
language: ScriptingLanguage,
|
|
) {
|
|
filterEmptyStrings([scriptCode.execute, scriptCode.revert])
|
|
.forEach(
|
|
(code) => validate(
|
|
code,
|
|
language,
|
|
[
|
|
CodeValidationRule.NoEmptyLines,
|
|
CodeValidationRule.NoDuplicatedLines,
|
|
CodeValidationRule.NoTooLongLines,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
function validateScript(
|
|
script: ScriptData,
|
|
validator: ExecutableValidator,
|
|
): asserts script is NonNullable<ScriptData> {
|
|
validator.assertType((v) => v.assertObject<CallScriptData & CodeScriptData>({
|
|
value: script,
|
|
valueName: script.name ? `Script '${script.name}'` : 'Script',
|
|
allowedProperties: [
|
|
'name', 'recommend', 'code', 'revertCode', 'call', 'docs',
|
|
],
|
|
}));
|
|
validator.assertValidName(script.name);
|
|
validator.assert(
|
|
() => Boolean((script as CodeScriptData).code || (script as CallScriptData).call),
|
|
'Neither "call" or "code" is defined.',
|
|
);
|
|
validator.assert(
|
|
() => !((script as CodeScriptData).code && (script as CallScriptData).call),
|
|
'Both "call" and "code" are defined.',
|
|
);
|
|
validator.assert(
|
|
() => !((script as CodeScriptData).revertCode && (script as CallScriptData).call),
|
|
'Both "call" and "revertCode" are defined.',
|
|
);
|
|
}
|
|
|
|
interface ScriptParserUtilities {
|
|
readonly levelParser: EnumParser<RecommendationLevel>;
|
|
readonly createScript: ScriptFactory;
|
|
readonly codeValidator: CodeValidator;
|
|
readonly wrapError: ErrorWithContextWrapper;
|
|
readonly createValidator: ExecutableValidatorFactory;
|
|
readonly createCode: ScriptCodeFactory;
|
|
readonly parseDocs: DocsParser;
|
|
}
|
|
|
|
const DefaultUtilities: ScriptParserUtilities = {
|
|
levelParser: createEnumParser(RecommendationLevel),
|
|
createScript,
|
|
codeValidator: validateCode,
|
|
wrapError: wrapErrorWithAdditionalContext,
|
|
createValidator: createExecutableDataValidator,
|
|
createCode: createScriptCode,
|
|
parseDocs,
|
|
};
|