Paul Gschwendtner 67f65a9d25 refactor(dev-infra): improve type-safety of git client utility (#42468)
Currently the `GitClient` accepts a generic parameter for determining
whether the `githubToken` should be set or not. This worked fine so far
in terms of distinguishing between an authenticated and
non-authenticated git client instance, but if we intend to conditionally
show methods only for authenticated instances, the generic parameter
is not suitable.

This commit splits up the `GitClient` into two classes. One for
the base logic without any authorization, and a second class that
extends the base logic with authentication logic. i.e. the
`AuthenticatedGitClient`. This allows us to have specific methods only
for the authenticated instance. e.g.

  * `hasOauthScopes` has been moved to only exist for authenticated
    instances.
  * the GraphQL functionality within `gitClient.github` is not
    accessible for non-authenticated instances. GraphQL API requires
    authentication as per Github.

The initial motiviation for this was that we want to throw if
`hasOAuthScopes` is called without the Octokit instance having
a token configured. This should help avoiding issues as within
3b434ed94d
that prevented the caretaker process momentarily.

Additionally, the Git client has moved from `index.ts` to
`git-client.ts` for better discoverability in the codebase.

PR Close #42468
2021-06-03 14:34:33 -07:00

102 lines
3.0 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {GitClient} from '../../utils/git/git-client';
import {FormatConfig} from '../config';
// A callback to determine if the formatter run found a failure in formatting.
export type CallbackFunc = (file: string, code: number, stdout: string, stderr: string) => boolean;
// The actions a formatter can take.
export type FormatterAction = 'check'|'format';
// The metadata needed for running one of the `FormatterAction`s on a file.
interface FormatterActionMetadata {
commandFlags: string;
callback: CallbackFunc;
}
/**
* The base class for formatters to run against provided files.
*/
export abstract class Formatter {
protected git = GitClient.get();
/**
* The name of the formatter, this is used for identification in logging and for enabling and
* configuring the formatter in the config.
*/
abstract name: string;
/** The full path file location of the formatter binary. */
abstract binaryFilePath: string;
/** Metadata for each `FormatterAction` available to the formatter. */
abstract actions: {
// An action performing a check of format without making any changes.
check: FormatterActionMetadata;
// An action to format files in place.
format: FormatterActionMetadata;
};
/** The default matchers for the formatter for filtering files to be formatted. */
abstract defaultFileMatcher: string[];
constructor(protected config: FormatConfig) {}
/**
* Retrieve the command to execute the provided action, including both the binary
* and command line flags.
*/
commandFor(action: FormatterAction) {
switch (action) {
case 'check':
return `${this.binaryFilePath} ${this.actions.check.commandFlags}`;
case 'format':
return `${this.binaryFilePath} ${this.actions.format.commandFlags}`;
default:
throw Error('Unknown action type');
}
}
/**
* Retrieve the callback for the provided action to determine if an action
* failed in formatting.
*/
callbackFor(action: FormatterAction) {
switch (action) {
case 'check':
return this.actions.check.callback;
case 'format':
return this.actions.format.callback;
default:
throw Error('Unknown action type');
}
}
/** Whether the formatter is enabled in the provided config. */
isEnabled() {
return !!this.config[this.name];
}
/** Retrieve the active file matcher for the formatter. */
getFileMatcher() {
return this.getFileMatcherFromConfig() || this.defaultFileMatcher;
}
/**
* Retrieves the file matcher from the config provided to the constructor if provided.
*/
private getFileMatcherFromConfig(): string[]|undefined {
const formatterConfig = this.config[this.name];
if (typeof formatterConfig === 'boolean') {
return undefined;
}
return formatterConfig.matchers;
}
}