Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion src/fixtures/runCustomFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface CustomFixture {
}[];
}

export const runCustomFixtures = ({ targetId, clientId, tests }: CustomFixture) => {
export const runCustomFixtures = ({ targetId, clientId, tests }: CustomFixture): void => {
describe(`custom fixtures for ${targetId}:${clientId}`, () => {
it.each(tests.map(t => [t.it, t]))('%s', async (_, { expected: fixtureFile, options, input: request }) => {
const opts: HTTPSnippetOptions = {};
Expand Down
12 changes: 6 additions & 6 deletions src/helpers/code-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class CodeBuilder {

indentationCharacter: string = DEFAULT_INDENTATION_CHARACTER;

lineJoin = DEFAULT_LINE_JOIN;
lineJoin: string = DEFAULT_LINE_JOIN;

/**
* Helper object to format and aggragate lines of code.
Expand All @@ -46,30 +46,30 @@ export class CodeBuilder {
/**
* Add the line at the beginning of the current lines
*/
unshift = (line: string, indentationLevel?: number) => {
unshift = (line: string, indentationLevel?: number): void => {
const newLine = this.indentLine(line, indentationLevel);
this.code.unshift(newLine);
};

/**
* Add the line at the end of the current lines
*/
push = (line: string, indentationLevel?: number) => {
push = (line: string, indentationLevel?: number): void => {
const newLine = this.indentLine(line, indentationLevel);
this.code.push(newLine);
};

/**
* Add an empty line at the end of current lines
*/
blank = () => {
blank = (): void => {
this.code.push('');
};

/**
* Concatenate all current lines using the given lineJoin, then apply any replacers that may have been added
*/
join = () => {
join = (): string => {
const unreplacedCode = this.code.join(this.lineJoin);
const replacedOutput = this.postProcessors.reduce((accumulator, replacer) => replacer(accumulator), unreplacedCode);
return replacedOutput;
Expand All @@ -79,7 +79,7 @@ export class CodeBuilder {
* Often when writing modules you may wish to add a literal tag or bit of metadata that you wish to transform after other processing as a final step.
* To do so, you can provide a PostProcessor function and it will be run automatically for you when you call `join()` later on.
*/
addPostProcessor = (postProcessor: PostProcessor) => {
addPostProcessor = (postProcessor: PostProcessor): void => {
this.postProcessors = [...this.postProcessors, postProcessor];
};
}
6 changes: 3 additions & 3 deletions src/helpers/escape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export interface EscapeOptions {
* See https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring
* for the complete original algorithm.
*/
export function escapeString(rawValue: any, options: EscapeOptions = {}) {
export function escapeString(rawValue: any, options: EscapeOptions = {}): string {
const { delimiter = '"', escapeChar = '\\', escapeNewlines = true } = options;

const stringValue = rawValue.toString();
Expand Down Expand Up @@ -79,7 +79,7 @@ export function escapeString(rawValue: any, options: EscapeOptions = {}) {
*
* If value is not a string, it will be stringified with .toString() first.
*/
export const escapeForSingleQuotes = (value: any) => escapeString(value, { delimiter: "'" });
export const escapeForSingleQuotes = (value: any): string => escapeString(value, { delimiter: "'" });

/**
* Make a string value safe to insert literally into a snippet within double quotes,
Expand All @@ -88,4 +88,4 @@ export const escapeForSingleQuotes = (value: any) => escapeString(value, { delim
*
* If value is not a string, it will be stringified with .toString() first.
*/
export const escapeForDoubleQuotes = (value: any) => escapeString(value, { delimiter: '"' });
export const escapeForDoubleQuotes = (value: any): string => escapeString(value, { delimiter: '"' });
8 changes: 4 additions & 4 deletions src/helpers/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ type Headers<T> = Record<string, T>;
/**
* Given a headers object retrieve a specific header out of it via a case-insensitive key.
*/
export const getHeaderName = <T>(headers: Headers<T>, name: string) =>
export const getHeaderName = <T>(headers: Headers<T>, name: string): string | undefined =>
Object.keys(headers).find(header => header.toLowerCase() === name.toLowerCase());

/**
* Given a headers object retrieve the contents of a header out of it via a case-insensitive key.
*/
export const getHeader = <T>(headers: Headers<T>, name: string) => {
export const getHeader = <T>(headers: Headers<T>, name: string): T | undefined => {
const headerName = getHeaderName(headers, name);
if (!headerName) {
return undefined;
Expand All @@ -20,12 +20,12 @@ export const getHeader = <T>(headers: Headers<T>, name: string) => {
/**
* Determine if a given case-insensitive header exists within a header object.
*/
export const hasHeader = <T>(headers: Headers<T>, name: string) => Boolean(getHeaderName(headers, name));
export const hasHeader = <T>(headers: Headers<T>, name: string): boolean => Boolean(getHeaderName(headers, name));

/**
* Determines if a given MIME type is JSON, or a variant of such.
*/
export const isMimeTypeJSON = (mimeType: string) =>
export const isMimeTypeJSON = (mimeType: string): boolean =>
['application/json', 'application/x-json', 'text/json', 'text/x-json', '+json'].some(
type => mimeType.indexOf(type) > -1,
);
5 changes: 4 additions & 1 deletion src/helpers/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export type ReducedHelperObject = Record<string, string[] | string>;

export const reducer = <T extends { name: string; value: string }>(accumulator: ReducedHelperObject, pair: T) => {
export const reducer = <T extends { name: string; value: string }>(
accumulator: ReducedHelperObject,
pair: T,
): ReducedHelperObject => {
const currentValue = accumulator[pair.name];
if (currentValue === undefined) {
accumulator[pair.name] = pair.value;
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Use 'strong quoting' using single quotes so that we only need to deal with nested single quote characters.
* see: http://wiki.bash-hackers.org/syntax/quoting#strong_quoting
*/
export const quote = (value = '') => {
export const quote = (value = ''): string => {
const safe = /^[a-z0-9-_/.@%^=:]+$/i;

const isShellSafe = safe.test(value);
Expand All @@ -15,4 +15,4 @@ export const quote = (value = '') => {
return `'${value.replace(/'/g, "'\\''")}'`;
};

export const escape = (value: string) => value.replace(/\r/g, '\\r').replace(/\n/g, '\\n');
export const escape = (value: string): string => value.replace(/\r/g, '\\r').replace(/\n/g, '\\n');
4 changes: 2 additions & 2 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ export interface AvailableTarget extends TargetInfo {
clients: ClientInfo[];
}

export const availableTargets = () =>
export const availableTargets = (): AvailableTarget[] =>
Object.keys(targets).map<AvailableTarget>(targetId => ({
...targets[targetId as TargetId].info,
clients: Object.keys(targets[targetId as TargetId].clientsById).map(
clientId => targets[targetId as TargetId].clientsById[clientId].info,
),
}));

export const extname = (targetId: TargetId, clientId: ClientId) => {
export const extname = (targetId: TargetId, clientId: ClientId): '' | `.${string}` => {
const target = targets[targetId];
if (!target) {
return '';
Expand Down
2 changes: 1 addition & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('HTTPSnippet', () => {
// @ts-expect-error intentionally incorrect
const result = snippet.convert(null);

expect(result).toBe(false);
expect(result).toStrictEqual([false]);
});

describe('repair malformed `postData`', () => {
Expand Down
35 changes: 28 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class HTTPSnippet {
}
}

init() {
init(): HTTPSnippet {
this.initCalled = true;

this.requests = this.entries.map(({ request }) => {
Expand Down Expand Up @@ -134,7 +134,28 @@ export class HTTPSnippet {
return this;
}

prepare(harRequest: HarRequest, options: HTTPSnippetOptions) {
prepare(
harRequest: HarRequest,
options: HTTPSnippetOptions,
): Request & {
allHeaders: Record<string, string[] | string>;
fullUrl: string;
url: string;
uriObj: {
query: ReducedHelperObject;
search: string;
path: string | null;
auth: string | null;
hash: string | null;
host: string | null;
hostname: string | null;
href: string;
pathname: string | null;
protocol: string | null;
slashes: boolean | null;
port: string | null;
};
} {
const request: Request = {
...harRequest,
fullUrl: '',
Expand Down Expand Up @@ -308,12 +329,12 @@ export class HTTPSnippet {
...urlWithParsedQuery,
query: null,
search: null,
}); //?
});

const fullUrl = urlFormat({
...urlWithParsedQuery,
...uriObj,
}); //?
});

return {
...request,
Expand All @@ -324,7 +345,7 @@ export class HTTPSnippet {
};
}

convert(targetId: TargetId, clientId?: ClientId, options?: Options) {
convert(targetId: TargetId, clientId?: ClientId, options?: Options): (string | false)[] {
if (!this.initCalled) {
this.init();
}
Expand All @@ -335,15 +356,15 @@ export class HTTPSnippet {

const target = targets[targetId];
if (!target) {
return false;
return [false];
}

const { convert } = target.clientsById[clientId || target.info.default];
const results = this.requests.map(request => convert(request, options));
return results;
}

installation(targetId: TargetId, clientId?: ClientId, options?: Options) {
installation(targetId: TargetId, clientId?: ClientId, options?: Options): (string | false)[] {
if (!this.initCalled) {
this.init();
}
Expand Down
29 changes: 25 additions & 4 deletions src/targets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,28 @@ export interface Target {
info: TargetInfo;
}

export const targets = {
type supportedTargets =
| 'c'
| 'clojure'
| 'csharp'
| 'go'
| 'http'
| 'java'
| 'javascript'
| 'json'
| 'kotlin'
| 'node'
| 'objc'
| 'ocaml'
| 'php'
| 'powershell'
| 'python'
| 'r'
| 'ruby'
| 'shell'
| 'swift';

export const targets: Record<supportedTargets, Target> = {
c,
clojure,
csharp,
Expand Down Expand Up @@ -181,7 +202,7 @@ export const isTarget = (target: Target): target is Target => {
return true;
};

export const addTarget = (target: Target) => {
export const addTarget = (target: Target): void => {
if (!isTarget(target)) {
return;
}
Expand Down Expand Up @@ -228,11 +249,11 @@ export const isClient = (client: Client): client is Client => {
return true;
};

export const addClientPlugin = (plugin: ClientPlugin) => {
export const addClientPlugin = (plugin: ClientPlugin): void => {
addTargetClient(plugin.target, plugin.client);
};

export const addTargetClient = (targetId: TargetId, client: Client) => {
export const addTargetClient = (targetId: TargetId, client: Client): void => {
if (!isClient(client)) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/targets/php/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { escapeString } from '../../helpers/escape.js';

export const convertType = (obj: any[] | any, indent?: string, lastIndent?: string) => {
export const convertType = (obj: any[] | any, indent?: string, lastIndent?: string): string | 'null' => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not just type this to string?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

i kinda like when return types explicitly mention a value that's likely to show up, in case a downstream function wants to act on that value accordingly. no strong preferences on this though

lastIndent = lastIndent || '';
indent = indent || '';

Expand Down Expand Up @@ -69,4 +69,4 @@ export const supportedMethods = [
'UNLOCK',
'UPDATE',
'VERSION_CONTROL',
];
] as const;
4 changes: 2 additions & 2 deletions src/targets/php/http1/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ export const http1: Client<Http1Options> = {
blank();
}

if (!supportedMethods.includes(method.toUpperCase())) {
if (!supportedMethods.includes(method.toUpperCase() as (typeof supportedMethods)[number])) {
push(`HttpRequest::methodRegister('${method}');`);
}

push('$request = new HttpRequest();');
push(`$request->setUrl(${convertType(url)});`);

if (supportedMethods.includes(method.toUpperCase())) {
if (supportedMethods.includes(method.toUpperCase() as (typeof supportedMethods)[number])) {
push(`$request->setMethod(HTTP_METH_${method.toUpperCase()});`);
} else {
push(`$request->setMethod(HttpRequest::HTTP_METH_${method.toUpperCase()});`);
Expand Down
4 changes: 2 additions & 2 deletions src/targets/powershell/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { getHeader } from '../../helpers/headers.js';

export type PowershellCommand = 'Invoke-RestMethod' | 'Invoke-WebRequest';

export const generatePowershellConvert = (command: PowershellCommand) => {
const convert: Converter<any> = ({ method, headersObj, cookies, uriObj, fullUrl, postData, allHeaders }) => {
export const generatePowershellConvert = (command: PowershellCommand): Converter<object> => {
const convert: Converter<object> = ({ method, headersObj, cookies, uriObj, fullUrl, postData, allHeaders }) => {
const { push, join } = new CodeBuilder();
const methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];

Expand Down
8 changes: 3 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
{
"compilerOptions": {
"allowJs": true,
"declaration": true,
"downlevelIteration": true,
"esModuleInterop": true,
"isolatedDeclarations": true,
"lib": ["DOM", "ES2020"],
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"outDir": "dist",
"resolveJsonModule": true,
"strict": true,
"target": "ES2020",

// Allows us to not have to typeguard in catches.
// https://bobbyhadz.com/blog/typescript-object-is-of-type-unknown
"useUnknownInCatchVariables": false,

"strict": true
"verbatimModuleSyntax": true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

haven't seen this one before, what's it do?

Copy link
Copy Markdown
Author

@kanadgupta kanadgupta Apr 9, 2025

Choose a reason for hiding this comment

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

we should probably enable it everywhere we can. it ensures that type imports use import type SomeType and not import SomeType, which will help us with treeshaking. as always mr. pocock has a good write-up on the subject in his cheat sheet: https://www.totaltypescript.com/tsconfig-cheat-sheet

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

oh cool

},
"exclude": ["dist/", "./src/fixtures/", "**/*.test.ts"],
"include": ["./src/**/*"]
Expand Down
Loading