Skip to content

Commit

Permalink
Bump to TypeScript 5.5 and enable noImplicitAny
Browse files Browse the repository at this point in the history
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:

- Add missing types for `electron-progressbar`
- 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.
  • Loading branch information
undergroundwires committed Sep 24, 2024
1 parent a05a600 commit bc3d4cc
Show file tree
Hide file tree
Showing 77 changed files with 12,958 additions and 11,862 deletions.
23,847 changes: 12,310 additions & 11,537 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "privacy.sexy",
"version": "0.13.6",
"version": "0.13.3",
"private": true,
"slogan": "Privacy is sexy",
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy.",
Expand Down Expand Up @@ -48,6 +48,7 @@
"@modyfi/vite-plugin-yaml": "^1.1.0",
"@rushstack/eslint-patch": "^1.10.3",
"@types/ace": "^0.0.52",
"@types/electron-progressbar": "^1.2.7",
"@types/file-saver": "^2.0.7",
"@types/markdown-it": "^14.1.1",
"@typescript-eslint/eslint-plugin": "6.21.0",
Expand Down Expand Up @@ -78,7 +79,7 @@
"start-server-and-test": "^2.0.4",
"terser": "^5.31.3",
"tslib": "^2.6.3",
"typescript": "^5.4.5",
"typescript": "^5.5.4",
"vite": "^5.3.4",
"vitest": "^2.0.3",
"vue-tsc": "^2.0.26",
Expand Down
10 changes: 6 additions & 4 deletions src/application/Common/Enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,25 @@ function parseEnumValue<T extends EnumType, TEnumValue extends EnumType>(
if (!casedValue) {
throw new Error(`unknown ${enumName}: "${value}"`);
}
return enumVariable[casedValue as keyof typeof enumVariable];
return enumVariable[casedValue as keyof EnumVariable<T, TEnumValue>];
}

export function getEnumNames
<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>,
): string[] {
): (string & keyof EnumVariable<T, TEnumValue>)[] {
return Object
.values(enumVariable)
.filter((enumMember): enumMember is string => isString(enumMember));
.filter((
enumMember,
): enumMember is string & (keyof EnumVariable<T, TEnumValue>) => isString(enumMember));
}

export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>,
): TEnumValue[] {
return getEnumNames(enumVariable)
.map((level) => enumVariable[level]) as TEnumValue[];
.map((name) => enumVariable[name]) as TEnumValue[];
}

export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
Expand Down
37 changes: 29 additions & 8 deletions src/application/Context/ApplicationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ApplicationContext implements IApplicationContext {
public currentOs: OperatingSystem;

public get state(): ICategoryCollectionState {
return this.states[this.collection.os];
return this.getState(this.collection.os);
}

private readonly states: StateMachine;
Expand All @@ -26,30 +26,51 @@ export class ApplicationContext implements IApplicationContext {
public readonly app: IApplication,
initialContext: OperatingSystem,
) {
this.setContext(initialContext);
this.states = initializeStates(app);
this.changeContext(initialContext);
}

public changeContext(os: OperatingSystem): void {
assertInRange(os, OperatingSystem);
if (this.currentOs === os) {
return;
}
const collection = this.app.getCollection(os);
this.collection = collection;
const event: IApplicationContextChangedEvent = {
newState: this.states[os],
oldState: this.states[this.currentOs],
newState: this.getState(os),
oldState: this.getState(this.currentOs),
};
this.setContext(os);
this.contextChanged.notify(event);
}

private setContext(os: OperatingSystem): void {
validateOperatingSystem(os, this.app);
this.collection = this.app.getCollection(os);
this.currentOs = os;
}

private getState(os: OperatingSystem): ICategoryCollectionState {
const state = this.states.get(os);
if (!state) {
throw new Error(`Operating system "${OperatingSystem[os]}" state is unknown.`);
}
return state;
}
}

function validateOperatingSystem(
os: OperatingSystem,
app: IApplication,
): void {
assertInRange(os, OperatingSystem);
if (!app.getSupportedOsList().includes(os)) {
throw new Error(`Operating system "${OperatingSystem[os]}" is not supported.`);
}
}

function initializeStates(app: IApplication): StateMachine {
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
for (const collection of app.collections) {
machine[collection.os] = new CategoryCollectionState(collection);
machine.set(collection.os, new CategoryCollectionState(collection));
}
return machine;
}
2 changes: 1 addition & 1 deletion src/application/Parser/Executable/CategoryParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function ensureValidCategory(
});
validator.assertType((v) => v.assertObject({
value: category,
valueName: `Category '${category.category}'` ?? 'Category',
valueName: category.category ? `Category '${category.category}'` : 'Category',
allowedProperties: [
'docs', 'children', 'category',
],
Expand Down
2 changes: 1 addition & 1 deletion src/application/Parser/Executable/Script/ScriptParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function validateScript(
): asserts script is NonNullable<ScriptData> {
validator.assertType((v) => v.assertObject<CallScriptData & CodeScriptData>({
value: script,
valueName: `Script '${script.name}'` ?? 'Script',
valueName: script.name ? `Script '${script.name}'` : 'Script',
allowedProperties: [
'name', 'recommend', 'code', 'revertCode', 'call', 'docs',
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type DuplicateLinesAnalyzer = CodeValidationAnalyzer & {
export const analyzeDuplicateLines: DuplicateLinesAnalyzer = (
lines: readonly CodeLine[],
language: ScriptingLanguage,
syntaxFactory = createSyntax,
syntaxFactory: SyntaxFactory = createSyntax,
) => {
const syntax = syntaxFactory(language);
return lines
Expand Down
8 changes: 4 additions & 4 deletions src/infrastructure/RuntimeSanity/Common/FactoryValidator.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { ISanityValidator } from './ISanityValidator';
import type { ISanityCheckOptions } from './ISanityCheckOptions';
import type { SanityValidator } from './SanityValidator';
import type { SanityCheckOptions } from './SanityCheckOptions';

export type FactoryFunction<T> = () => T;

export abstract class FactoryValidator<T> implements ISanityValidator {
export abstract class FactoryValidator<T> implements SanityValidator {
private readonly factory: FactoryFunction<T>;

protected constructor(factory: FactoryFunction<T>) {
this.factory = factory;
}

public abstract shouldValidate(options: ISanityCheckOptions): boolean;
public abstract shouldValidate(options: SanityCheckOptions): boolean;

public abstract name: string;

Expand Down
7 changes: 0 additions & 7 deletions src/infrastructure/RuntimeSanity/Common/ISanityValidator.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ISanityCheckOptions {
export interface SanityCheckOptions {
readonly validateEnvironmentVariables: boolean;
readonly validateWindowVariables: boolean;
}
7 changes: 7 additions & 0 deletions src/infrastructure/RuntimeSanity/Common/SanityValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { SanityCheckOptions } from './SanityCheckOptions';

export interface SanityValidator {
readonly name: string;
shouldValidate(options: SanityCheckOptions): boolean;
collectErrors(): Iterable<string>;
}
25 changes: 16 additions & 9 deletions src/infrastructure/RuntimeSanity/SanityChecks.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { EnvironmentVariablesValidator } from './Validators/EnvironmentVariablesValidator';
import type { ISanityCheckOptions } from './Common/ISanityCheckOptions';
import type { ISanityValidator } from './Common/ISanityValidator';
import type { SanityCheckOptions } from './Common/SanityCheckOptions';
import type { SanityValidator } from './Common/SanityValidator';

const DefaultSanityValidators: ISanityValidator[] = [
const DefaultSanityValidators: SanityValidator[] = [
new EnvironmentVariablesValidator(),
];

export interface RuntimeSanityValidator {
(
options: SanityCheckOptions,
validators?: readonly SanityValidator[],
): void;
}

/* Helps to fail-fast on errors */
export function validateRuntimeSanity(
options: ISanityCheckOptions,
validators: readonly ISanityValidator[] = DefaultSanityValidators,
): void {
export const validateRuntimeSanity: RuntimeSanityValidator = (
options: SanityCheckOptions,
validators: readonly SanityValidator[] = DefaultSanityValidators,
) => {
if (!validators.length) {
throw new Error('missing validators');
}
Expand All @@ -26,9 +33,9 @@ export function validateRuntimeSanity(
if (errorMessages.length > 0) {
throw new Error(`Sanity check failed.\n${errorMessages.join('\n---\n')}`);
}
}
};

function getErrorMessage(validator: ISanityValidator): string | undefined {
function getErrorMessage(validator: SanityValidator): string | undefined {
const errorMessages = [...validator.collectErrors()];
if (!errorMessages.length) {
return undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IEnvironmentVariables } from '@/infrastructure/EnvironmentVariables/IEnvironmentVariables';
import { EnvironmentVariablesFactory } from '@/infrastructure/EnvironmentVariables/EnvironmentVariablesFactory';
import { FactoryValidator, type FactoryFunction } from '../Common/FactoryValidator';
import type { ISanityCheckOptions } from '../Common/ISanityCheckOptions';
import type { SanityCheckOptions } from '../Common/SanityCheckOptions';

export class EnvironmentVariablesValidator extends FactoryValidator<IEnvironmentVariables> {
constructor(
Expand All @@ -14,7 +14,7 @@ export class EnvironmentVariablesValidator extends FactoryValidator<IEnvironment

public override name = 'environment variables';

public override shouldValidate(options: ISanityCheckOptions): boolean {
public override shouldValidate(options: SanityCheckOptions): boolean {
return options.validateEnvironmentVariables;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { WindowVariables } from '@/infrastructure/WindowVariables/WindowVariables';
import { FactoryValidator, type FactoryFunction } from '../Common/FactoryValidator';
import type { ISanityCheckOptions } from '../Common/ISanityCheckOptions';
import type { SanityCheckOptions } from '../Common/SanityCheckOptions';

export class WindowVariablesValidator extends FactoryValidator<WindowVariables> {
constructor(factory: FactoryFunction<WindowVariables> = () => window) {
Expand All @@ -9,7 +9,7 @@ export class WindowVariablesValidator extends FactoryValidator<WindowVariables>

public override name = 'window variables';

public override shouldValidate(options: ISanityCheckOptions): boolean {
public override shouldValidate(options: SanityCheckOptions): boolean {
return options.validateWindowVariables;
}
}
4 changes: 2 additions & 2 deletions src/presentation/bootstrapping/ApplicationBootstrapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator';
import { RuntimeSanityBootstrapper } from './Modules/RuntimeSanityBootstrapper';
import { AppInitializationLogger } from './Modules/AppInitializationLogger';
import { DependencyBootstrapper } from './Modules/DependencyBootstrapper';
import { MobileSafariActivePseudoClassEnabler } from './Modules/MobileSafariActivePseudoClassEnabler';
Expand All @@ -17,7 +17,7 @@ export class ApplicationBootstrapper implements Bootstrapper {

private static getAllBootstrappers(): Bootstrapper[] {
return [
new RuntimeSanityValidator(),
new RuntimeSanityBootstrapper(),
new DependencyBootstrapper(),
new AppInitializationLogger(),
new MobileSafariActivePseudoClassEnabler(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { validateRuntimeSanity, type RuntimeSanityValidator } from '@/infrastructure/RuntimeSanity/SanityChecks';
import type { Bootstrapper } from '../Bootstrapper';

export class RuntimeSanityBootstrapper implements Bootstrapper {
constructor(private readonly validator: RuntimeSanityValidator = validateRuntimeSanity) {

}

public async bootstrap(): Promise<void> {
this.validator({
validateEnvironmentVariables: true,
validateWindowVariables: true,
});
}
}
15 changes: 0 additions & 15 deletions src/presentation/bootstrapping/Modules/RuntimeSanityValidator.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
:tree-root="treeRoot"
:rendering-strategy="renderingStrategy"
>
<template #node-content="slotProps">
<template #node-content="slotProps: NodeMetadata">
<slot name="node-content" v-bind="slotProps" />
</template>
</HierarchicalTreeNode>
Expand All @@ -55,6 +55,7 @@ import { useCurrentTreeNodes } from '../UseCurrentTreeNodes';
import { useNodeState } from './UseNodeState';
import LeafTreeNode from './LeafTreeNode.vue';
import InteractableNode from './InteractableNode.vue';
import type { NodeMetadata } from '../../NodeContent/NodeMetadata';
import type { TreeRoot } from '../TreeRoot/TreeRoot';
import type { TreeNode, TreeNodeId } from './TreeNode';
import type { NodeRenderingStrategy } from '../Rendering/Scheduling/NodeRenderingStrategy';
Expand Down Expand Up @@ -107,6 +108,7 @@ export default defineComponent({
);
return {
NodeMetadata: Object as PropType<NodeMetadata>,
renderedNodeIds,
isExpanded,
toggleExpand,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ export class TreeRootManager implements TreeRoot {

constructor(
collection: TreeNodeCollection = new TreeNodeInitializerAndUpdater(),
createFocusManager: (
collection: TreeNodeCollection
) => SingleNodeFocusManager = (nodes) => new SingleNodeCollectionFocusManager(nodes),
createFocusManager: FocusManagerFactory = (
nodes,
) => new SingleNodeCollectionFocusManager(nodes),
) {
this.collection = collection;
this.focus = createFocusManager(this.collection);
}
}

export interface FocusManagerFactory {
(
collection: TreeNodeCollection
): SingleNodeFocusManager;
}
18 changes: 8 additions & 10 deletions src/presentation/components/Shared/TooltipWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,15 @@ function getArrowPositionStyles(
coordinations: Partial<Coords>,
placement: Placement,
): CSSProperties {
const style: CSSProperties = {};
style.position = 'absolute';
const { x, y } = coordinations;
if (x) {
style.left = `${x}px`;
} else if (y) { // either X or Y is calculated
style.top = `${y}px`;
}
const { x, y } = coordinations; // either X or Y is calculated
const oppositeSide = getCounterpartBoxOffsetProperty(placement);
style[oppositeSide.toString()] = `-${ARROW_SIZE_IN_PX}px`;
return style;
const newStyle: CSSProperties = {
[oppositeSide]: `-${ARROW_SIZE_IN_PX}px`,
position: 'absolute',
left: x ? `${x}px` : undefined,
top: y ? `${y}px` : undefined,
};
return newStyle;
}
function getCounterpartBoxOffsetProperty(placement: Placement): keyof CSSProperties {
Expand Down
3 changes: 2 additions & 1 deletion src/presentation/electron/main/IpcRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export function registerAllIpcChannels(
};
Object.entries(ipcInstanceCreators).forEach(([name, instanceFactory]) => {
try {
const definition = IpcChannelDefinitions[name];
const definitionKey = name as keyof typeof IpcChannelDefinitions;
const definition = IpcChannelDefinitions[definitionKey] as IpcChannel<unknown>;
const instance = instanceFactory();
registrar(definition, instance);
} catch (err) {
Expand Down
Loading

0 comments on commit bc3d4cc

Please sign in to comment.