Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,19 +1082,44 @@ export class Analyzer {
};
return {ok: false, error: this._markAsInvalid(config, failure)};
}
{
const validConfig: ScriptConfig = {

let validConfig: ScriptConfig;
if (config.service) {
// We should already have created an invalid script at this point, so we
// should never get here. We throw here to convince TypeScript that this
// is guaranteed.
if (config.command === undefined) {
throw new Error(
'Internal error: Supposedly valid service did not have command'
);
}
validConfig = {
...config,
state: 'valid',
extraArgs: undefined,
dependencies: config.dependencies as Array<Dependency<ScriptConfig>>,
// Unfortunately TypeScript doesn't narrow the ...config spread, so we
// have to assign explicitly.
command: config.command,
};
} else {
validConfig = {
...config,
state: 'valid',
extraArgs: undefined,
dependencies: config.dependencies as Array<Dependency<ScriptConfig>>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this cast needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because that property requires validated dependencies, but the type checker still believes they could be unvalidated, because that's checked earlier in a way that doesn't cause the type checker to narrow.

// Unfortunately TypeScript doesn't narrow the ...config spread, so we
// have to assign explicitly.
service: config.service,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you cast the config: ...(config as ServiceScriptConfig)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but then we'd be bypassing the type checker all together. Here I'm trying to prove to the type checker (and hence ourselves) that the 3 different configs can be discriminated.

};
// We want to keep the original reference, but get type checking that
// the only difference between a ScriptConfig and a
// LocallyValidScriptConfig is that the state is 'valid' and the
// dependencies are also valid, which we confirmed above.
Object.assign(config, validConfig);
}

// We want to keep the original reference, but get type checking that
// the only difference between a ScriptConfig and a
// LocallyValidScriptConfig is that the state is 'valid' and the
// dependencies are also valid, which we confirmed above.
Object.assign(config, validConfig);

return {ok: true, value: config as unknown as ScriptConfig};
}

Expand Down
42 changes: 32 additions & 10 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,30 @@ export interface ScriptReference extends PackageReference {
name: string;
}

/**
* A script with a defined command.
*/
export interface ScriptReferenceWithCommand extends ScriptReference {
/**
* The shell command to execute.
*/
command: JsonAstNode<string>;

/**
* Extra arguments to pass to the command.
*/
extraArgs: string[] | undefined;
}

export interface Dependency<Config extends PotentiallyValidScriptConfig> {
config: Config;
astNode: JsonAstNode<string>;
}

export type ScriptConfig = NoCommandScriptConfig | StandardScriptConfig;
export type ScriptConfig =
| NoCommandScriptConfig
| StandardScriptConfig
| ServiceScriptConfig;

/**
* A script that doesn't run or produce anything. A pass-through for
Expand All @@ -43,21 +61,25 @@ export type ScriptConfig = NoCommandScriptConfig | StandardScriptConfig;
export interface NoCommandScriptConfig extends BaseScriptConfig {
command: undefined;
extraArgs: undefined;
service: false;
}

/**
* A script with a command that exits by itself.
*/
export interface StandardScriptConfig extends BaseScriptConfig {
/**
* The shell command to execute.
*/
command: JsonAstNode<string>;
export interface StandardScriptConfig
extends BaseScriptConfig,
ScriptReferenceWithCommand {
service: false;
}

/**
* Extra arguments to pass to the command.
*/
extraArgs: string[] | undefined;
/**
* A service script.
*/
export interface ServiceScriptConfig
extends BaseScriptConfig,
ScriptReferenceWithCommand {
service: true;
}

/**
Expand Down
Loading