report Bash parsing errors (#399)

* allow passing warnings list to *Warn() functions

that way if there's an error, we can catch the error, look at the
warnings that happened while parsing and show them to users.
This commit is contained in:
Boris Verkhovskiy
2022-04-24 04:57:03 -07:00
committed by GitHub
parent a29f9bc259
commit 39f01a5a47
23 changed files with 253 additions and 209 deletions

View File

@@ -8,7 +8,8 @@
"prettier"
],
"rules": {
"no-empty": ["error", { "allowEmptyCatch": true }]
"no-empty": ["error", { "allowEmptyCatch": true }],
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
},
"parserOptions": {
"ecmaVersion": 2018,

View File

@@ -54,8 +54,8 @@ const defaultLanguage = "python";
// NOTE: make sure to update this when adding language support
const translate: {
[key: string]: [
(request: Request, warnings?: Warnings) => [string, Warnings],
(curlCommand: string | string[]) => [string, [string, string][]]
(request: Request, warnings?: Warnings) => string,
(curlCommand: string | string[], warnings?: Warnings) => [string, Warnings]
];
} = {
ansible: [_toAnsible, toAnsibleWarn],
@@ -120,6 +120,17 @@ const curlConverterShortOpts: ShortOpts = {
const longOpts: LongOpts = { ...curlLongOpts, ...curlConverterLongOpts };
const shortOpts: ShortOpts = { ...curlShortOpts, ...curlConverterShortOpts };
function printWarnings(warnings: Warnings, verbose: boolean): Warnings {
if (!verbose) {
return warnings;
}
for (const w of warnings) {
for (const line of w[1].trim().split("\n")) {
console.error("warning: " + line);
}
}
return [];
}
function exitWithError(error: unknown, verbose = false): never {
let errMsg: Error | string | unknown = error;
if (!verbose) {
@@ -139,9 +150,10 @@ function exitWithError(error: unknown, verbose = false): never {
}
const argv = process.argv.slice(2);
let parsedArguments, warnings;
let parsedArguments;
let warnings: Warnings = [];
try {
[parsedArguments, warnings] = parseArgs(argv, longOpts, shortOpts);
parsedArguments = parseArgs(argv, longOpts, shortOpts, undefined, warnings);
} catch (e) {
exitWithError(e);
}
@@ -153,6 +165,7 @@ if (parsedArguments.version) {
console.log("curlconverter " + VERSION);
process.exit(0);
}
const verbose = parsedArguments.verbose;
const argc = Object.keys(parsedArguments).length;
const language = parsedArguments.language || defaultLanguage;
@@ -166,7 +179,7 @@ if (!has(translate, language)) {
"must be one of: " +
Object.keys(translate).join(", ")
),
parsedArguments.verbose
verbose
);
}
for (const opt of Object.keys(curlConverterLongOpts)) {
@@ -192,22 +205,26 @@ if (stdin) {
new CCError(
"if you pass --stdin or -, you can't also pass " + extraArgsStr
),
parsedArguments.verbose
verbose
);
}
const input = fs.readFileSync(0, "utf8");
try {
[code, warnings] = warnGenerator(input);
[code, warnings] = warnGenerator(input, warnings);
} catch (e) {
exitWithError(e, parsedArguments.verbose);
printWarnings(warnings, true); // print warnings to help figure out the error
exitWithError(e, verbose);
}
warnings = printWarnings(warnings, verbose);
} else {
warnings = printWarnings(warnings, verbose);
let request;
try {
request = buildRequest(parsedArguments, warnings);
} catch (e) {
exitWithError(e, parsedArguments.verbose);
exitWithError(e, verbose);
}
warnings = printWarnings(warnings, verbose);
// Warning for users using the pre-4.0 CLI
if (request.url?.startsWith("curl ")) {
console.error(
@@ -221,15 +238,12 @@ if (stdin) {
);
}
try {
[code, warnings] = generator(request, warnings);
code = generator(request, warnings);
} catch (e) {
exitWithError(e, parsedArguments.verbose);
exitWithError(e, verbose);
}
warnings = printWarnings(warnings, verbose);
}
if (warnings && parsedArguments.verbose) {
for (const w of warnings) {
console.error("warning: " + w[1]);
}
}
printWarnings(warnings, verbose);
process.stdout.write(code);

View File

@@ -48,9 +48,8 @@ function getDataString(request: Request): string | object {
export const _toAnsible = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = [] // eslint-disable-line @typescript-eslint/no-unused-vars
): string => {
let convertedData;
if (request.data && typeof request.data === "string") {
convertedData = getDataString(request);
@@ -59,13 +58,15 @@ export const _toAnsible = (
request,
data: convertedData,
});
return [result, warnings];
return result;
};
export const toAnsibleWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toAnsible(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const ansible = _toAnsible(request, warnings);
return [ansible, warnings];
};
export const toAnsible = (curlCommand: string | string[]): string => {
return toAnsibleWarn(curlCommand)[0];

View File

@@ -31,11 +31,7 @@ const quote = (str: string): string => {
return jsesc(str, { quotes: "single" }).replace(/"/g, '""');
};
export const _toCFML = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toCFML = (request: Request, warnings: Warnings = []): string => {
let cfmlCode = "";
cfmlCode += "httpService = new http();\n";
@@ -137,14 +133,16 @@ export const _toCFML = (
cfmlCode += "\nresult = httpService.send().getPrefix();\n";
cfmlCode += "writeDump(result);\n";
return [cfmlCode, warnings];
return cfmlCode;
};
export const toCFMLWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toCFML(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const cfml = _toCFML(request, warnings);
return [cfml, warnings];
};
export const toCFML = (curlCommand: string | string[]): string => {

View File

@@ -34,11 +34,7 @@ function repr(value: string): string {
}
}
export const _toDart = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toDart = (request: Request, warnings: Warnings = []): string => {
let s = "";
if (request.auth || request.isDataBinary) s += "import 'dart:convert';\n";
@@ -144,13 +140,15 @@ export const _toDart = (
" print(res.body);\n" +
"}";
return [s + "\n", warnings];
return s + "\n";
};
export const toDartWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toDart(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const dart = _toDart(request, warnings);
return [dart, warnings];
};
export const toDart = (curlCommand: string | string[]): string => {
return toDartWarn(curlCommand)[0];

View File

@@ -229,10 +229,8 @@ ${data.join(",\n")}
export const _toElixir = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
if (request.cookies) {
util.deleteHeader(request, "cookie");
}
@@ -249,13 +247,15 @@ export const _toElixir = (
response = HTTPoison.request(request)
`;
return [template, warnings];
return template;
};
export const toElixirWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toElixir(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const elixir = _toElixir(request, warnings);
return [elixir, warnings];
};
export const toElixir = (curlCommand: string | string[]): string => {

View File

@@ -35,10 +35,7 @@ const repr = (s: string): string => {
return '"' + jsesc(s, { quotes: "double", minimal: true }) + '"';
};
export const _toGo = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
export const _toGo = (request: Request, warnings: Warnings = []): string => {
warnings = warnings || [];
let goCode = "package main\n\n";
goCode += "import (\n";
@@ -94,13 +91,15 @@ export const _toGo = (
goCode += '\tfmt.Printf("%s\\n", bodyText)\n';
goCode += "}";
return [goCode + "\n", warnings];
return goCode + "\n";
};
export const toGoWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toGo(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const go = _toGo(request, warnings);
return [go, warnings];
};
export const toGo = (curlCommand: string | string[]): string => {
return toGoWarn(curlCommand)[0];

View File

@@ -27,11 +27,7 @@ const supportedArgs = new Set([
const doubleQuotes = (str: string): string => jsesc(str, { quotes: "double" });
export const _toJava = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toJava = (request: Request, warnings: Warnings = []): string => {
let javaCode = "";
if (request.auth) {
@@ -116,13 +112,15 @@ export const _toJava = (
javaCode += "\t}\n";
javaCode += "}";
return [javaCode + "\n", warnings];
return javaCode + "\n";
};
export const toJavaWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toJava(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const java = _toJava(request, warnings);
return [java, warnings];
};
export const toJava = (curlCommand: string | string[]): string => {

View File

@@ -234,10 +234,8 @@ const buildConfigObject = (
export const _toNodeAxios = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
let importCode = "const axios = require('axios');\n";
const imports: Set<[string, string]> = new Set();
@@ -377,13 +375,15 @@ export const _toNodeAxios = (
importCode += "const " + varName + " = require(" + repr(imp) + ");\n";
}
return [importCode + "\n" + code, warnings];
return importCode + "\n" + code;
};
export const toNodeAxiosWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toNodeAxios(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const nodeAxios = _toNodeAxios(request, warnings);
return [nodeAxios, warnings];
};
export const toNodeAxios = (curlCommand: string | string[]): string => {
return toNodeAxiosWarn(curlCommand)[0];

View File

@@ -123,7 +123,7 @@ export const _toJavaScriptOrNode = (
request: Request,
warnings: Warnings,
isNode: boolean
): [string, Warnings] => {
): string => {
const fetchImports: Set<string> = new Set();
const imports: Set<[string, string]> = new Set();
@@ -307,47 +307,44 @@ export const _toJavaScriptOrNode = (
if (importCode) {
code = importCode + "\n" + code;
}
return [code + "\n", warnings];
return code + "\n";
};
export const _toJavaScript = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
return _toJavaScriptOrNode(request, warnings, false);
};
export const _toNode = (request: Request, warnings: Warnings = []): string => {
return _toJavaScriptOrNode(request, warnings, true);
};
export const toJavaScriptWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(
const request = util.parseCurlCommand(
curlCommand,
javaScriptSupportedArgs
javaScriptSupportedArgs,
warnings
);
return _toJavaScript(request, warnings);
return [_toJavaScript(request, warnings), warnings];
};
export const toJavaScript = (curlCommand: string | string[]): string => {
return toJavaScriptWarn(curlCommand)[0];
};
export const _toNode = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
return _toJavaScriptOrNode(request, warnings, true);
};
export const toNodeWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(
const request = util.parseCurlCommand(
curlCommand,
nodeSupportedArgs
nodeSupportedArgs,
warnings
);
return _toNode(request, warnings);
return [_toNode(request, warnings), warnings];
};
export const toNode = (curlCommand: string | string[]): string => {
return toNodeWarn(curlCommand)[0];

View File

@@ -26,9 +26,8 @@ const supportedArgs = new Set([
export const _toNodeRequest = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
let nodeRequestCode = "var request = require('request');\n\n";
if (request.headers) {
nodeRequestCode += "var headers = {\n";
@@ -87,14 +86,17 @@ export const _toNodeRequest = (
nodeRequestCode += "}\n\n";
nodeRequestCode += "request(options, callback);";
return [nodeRequestCode + "\n", warnings];
return nodeRequestCode + "\n";
};
export const toNodeRequestWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
warnings.unshift(["node-request", "the request package is deprecated"]);
return _toNodeRequest(request, warnings);
const nodeRequests = _toNodeRequest(request, warnings);
return [nodeRequests, warnings];
};
export const toNodeRequest = (curlCommand: string | string[]): string => {
return toNodeRequestWarn(curlCommand)[0];

View File

@@ -122,10 +122,8 @@ function getFilesString(
export const _toJsonString = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
const requestJson: JSONOutput = {
url: (request.queryDict ? request.urlWithoutQuery : request.url).replace(
/\/$/,
@@ -177,20 +175,21 @@ export const _toJsonString = (
};
}
return [
return (
JSON.stringify(
Object.keys(requestJson).length ? requestJson : "{}",
null,
4
) + "\n",
warnings,
];
) + "\n"
);
};
export const toJsonStringWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toJsonString(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const json = _toJsonString(request, warnings);
return [json, warnings];
};
export const toJsonString = (curlCommand: string | string[]): string => {
return toJsonStringWarn(curlCommand)[0];

View File

@@ -29,26 +29,24 @@ const supportedArgs = new Set([
export const _toMATLAB = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
let webServicesLines, httpInterfaceLines;
[webServicesLines, warnings] = toWebServices(request, warnings);
[httpInterfaceLines, warnings] = toHTTPInterface(request, warnings);
const lines = webServicesLines.concat("", httpInterfaceLines);
return [
lines
.flat()
.filter((line) => line !== null)
.join("\n"),
warnings,
];
return lines
.flat()
.filter((line) => line !== null)
.join("\n");
};
export const toMATLABWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toMATLAB(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const matlab = _toMATLAB(request, warnings);
return [matlab, warnings];
};
export const toMATLAB = (curlCommand: string | string[]): string => {
return toMATLABWarn(curlCommand)[0];

View File

@@ -31,9 +31,8 @@ const quote = (str: string | null | (string | null)[]): string =>
export const _toPhpRequests = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
let headerString: string;
if (request.headers) {
headerString = "$headers = array(\n";
@@ -113,13 +112,15 @@ export const _toPhpRequests = (
phpCode += requestLine;
return [phpCode + "\n", warnings];
return phpCode + "\n";
};
export const toPhpRequestsWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toPhpRequests(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const php = _toPhpRequests(request, warnings);
return [php, warnings];
};
export const toPhpRequests = (curlCommand: string | string[]): string => {
return toPhpRequestsWarn(curlCommand)[0];

View File

@@ -36,11 +36,7 @@ const supportedArgs = new Set([
const quote = (str: string): string => jsesc(str, { quotes: "single" });
export const _toPhp = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toPhp = (request: Request, warnings: Warnings = []): string => {
let cookieString;
if (util.hasHeader(request, "cookie")) {
cookieString = util.getHeader(request, "cookie");
@@ -157,14 +153,16 @@ export const _toPhp = (
phpCode += "\n$response = curl_exec($ch);\n\n";
phpCode += "curl_close($ch);\n";
return [phpCode, warnings];
return phpCode;
};
export const toPhpWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toPhp(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const php = _toPhp(request, warnings);
return [php, warnings];
};
export const toPhp = (curlCommand: string | string[]): string => {
return toPhpWarn(curlCommand)[0];

View File

@@ -667,9 +667,8 @@ function detectEnvVar(inputString: string): [Set<string>, string] {
export const _toPython = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
const imports: Set<string> = new Set();
// Currently, only assuming that the env-var only used in
// the value part of cookies, params, or body
@@ -1024,17 +1023,18 @@ export const _toPython = (
]);
}
return [pythonCode + "\n", warnings];
return pythonCode + "\n";
};
export const toPythonWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toPython(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const python = _toPython(request, warnings);
return [python, warnings];
};
export const toPython = (curlCommand: string | string[]): string => {
const [request, warnings] = util.parseCurlCommand(curlCommand);
return _toPython(request, warnings)[0];
return toPythonWarn(curlCommand)[0];
};

View File

@@ -92,11 +92,7 @@ function getFilesString(request: Request): string | undefined {
return filesString;
}
export const _toR = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toR = (request: Request, warnings: Warnings = []): string => {
let cookieDict;
if (request.cookies) {
cookieDict = "cookies = c(\n";
@@ -223,11 +219,15 @@ export const _toR = (
}
rstatsCode += requestLine;
return [rstatsCode + "\n", warnings];
return rstatsCode + "\n";
};
export const toRWarn = (curlCommand: string | string[]): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toR(request, warnings);
export const toRWarn = (
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const r = _toR(request, warnings);
return [r, warnings];
};
export const toR = (curlCommand: string | string[]): string => {
return toRWarn(curlCommand)[0];

View File

@@ -29,11 +29,7 @@ const indent = (line: string, level = 1): string =>
INDENTATION.repeat(level) + line;
const quote = (str: string): string => jsesc(str, { quotes: "double" });
export const _toRust = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
export const _toRust = (request: Request, warnings: Warnings = []): string => {
const lines = ["extern crate reqwest;"];
{
// Generate imports.
@@ -121,13 +117,15 @@ export const _toRust = (
"}"
);
return [lines.join("\n") + "\n", warnings];
return lines.join("\n") + "\n";
};
export const toRustWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toRust(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const rust = _toRust(request, warnings);
return [rust, warnings];
};
export const toRust = (curlCommand: string | string[]): string => {
return toRustWarn(curlCommand)[0];

View File

@@ -94,9 +94,8 @@ type StrestOutput = {
export const _toStrest = (
request: Request,
warnings?: Warnings
): [string, Warnings] => {
warnings = warnings || [];
warnings: Warnings = []
): string => {
const response: StrestOutput = { version: 2 };
if (request.insecure) {
response.allowInsecure = true;
@@ -139,14 +138,15 @@ export const _toStrest = (
response.requests.curl_converter.request.queryString = queryList;
}
const yamlString = yaml.stringify(response, 100, 2);
return [yamlString, warnings];
return yaml.stringify(response, 100, 2);
};
export const toStrestWarn = (
curlCommand: string | string[]
curlCommand: string | string[],
warnings: Warnings = []
): [string, Warnings] => {
const [request, warnings] = util.parseCurlCommand(curlCommand, supportedArgs);
return _toStrest(request, warnings);
const request = util.parseCurlCommand(curlCommand, supportedArgs, warnings);
const strest = _toStrest(request, warnings);
return [strest, warnings];
};
export const toStrest = (curlCommand: string | string[]): string => {
return toStrestWarn(curlCommand)[0];

View File

@@ -63,12 +63,6 @@ interface ShortOpts {
[key: string]: string;
}
interface PossibleArgs {
supported: string[];
unsupported: string[];
ignored: string[];
}
type Query = Array<[string, string | null]>;
interface QueryDict {
[key: string]: string | null | Array<string | null>;
@@ -966,13 +960,34 @@ function toVal(node: any): string {
}
}
const reportError = (
curlCommand: string,
startCol: number,
startRow: number,
endCol: number,
endRow: number
): string => {
// TODO: is this exactly how tree-sitter splits lines?
const line = curlCommand.split("\n")[startRow];
const end = endRow === startRow ? endCol : line.length;
return (
`Bash parsing error on line ${startRow + 1}:\n` +
`${line}\n` +
" ".repeat(startCol) +
"^".repeat(end - startCol)
);
};
interface TokenizeResult {
cmdName: string;
args: string[];
stdin?: string;
input?: string;
}
const tokenize = (curlCommand: string): TokenizeResult => {
const tokenize = (
curlCommand: string,
warnings: Warnings = []
): TokenizeResult => {
const curlArgs = parser.parse(curlCommand);
// The AST must be in a nice format, i.e.
// (program
@@ -1004,20 +1019,20 @@ const tokenize = (curlCommand: string): TokenizeResult => {
// Get the curl call AST node. Skip comments
let command, stdin, input;
for (const programChildNode of curlArgs.rootNode.children) {
if (programChildNode.type === "comment") {
for (const n of curlArgs.rootNode.children) {
if (n.type === "comment") {
continue;
} else if (programChildNode.type === "command") {
command = programChildNode;
} else if (n.type === "command") {
command = n;
// TODO: if there are more `command` nodes,
// warn that everything after the first one is ignored
break;
} else if (programChildNode.type === "redirected_statement") {
if (!programChildNode.childCount) {
} else if (n.type === "redirected_statement") {
if (!n.childCount) {
throw new CCError("got empty 'redirected_statement' AST node");
}
let redirect;
[command, redirect] = programChildNode.children;
[command, redirect] = n.children;
if (command.type !== "command") {
throw new CCError(
"got 'redirected_statement' AST node whose first child is not a 'command', got " +
@@ -1025,7 +1040,7 @@ const tokenize = (curlCommand: string): TokenizeResult => {
" instead"
);
}
if (programChildNode.childCount < 2) {
if (n.childCount < 2) {
throw new CCError(
"got 'redirected_statement' AST node with only one child - no redirect"
);
@@ -1041,7 +1056,7 @@ const tokenize = (curlCommand: string): TokenizeResult => {
);
}
const heredocStart = redirect.namedChildren[0].text;
const heredocBody = programChildNode.nextNamedSibling;
const heredocBody = n.nextNamedSibling;
if (!heredocBody) {
throw new CCError(
"got 'redirected_statement' AST node with no heredoc body"
@@ -1081,6 +1096,16 @@ const tokenize = (curlCommand: string): TokenizeResult => {
}
break;
} else if (n.type === "ERROR") {
throw new CCError(
reportError(
curlCommand,
n.startPosition.column,
n.startPosition.row,
n.endPosition.column,
n.endPosition.row
)
);
} else {
// TODO: better error message.
throw new CCError(
@@ -1096,6 +1121,20 @@ const tokenize = (curlCommand: string): TokenizeResult => {
"expected a 'command' or 'redirected_statement' AST node, only found 'comment' nodes"
);
}
for (const n of curlArgs.rootNode.children) {
if (n.type === "ERROR") {
warnings.push([
"bash",
reportError(
curlCommand,
n.startPosition.column,
n.startPosition.row,
n.endPosition.column,
n.endPosition.row
),
]);
}
}
if (command.childCount < 1) {
// TODO: better error message.
@@ -1149,9 +1188,9 @@ const parseArgs = (
args: string[],
longOpts: LongOpts,
shortOpts: ShortOpts,
supportedOpts?: Set<string>
): [ParsedArguments, Warnings] => {
const warnings: Warnings = [];
supportedOpts?: Set<string>,
warnings: Warnings = []
): ParsedArguments => {
const parsedArguments: ParsedArguments = {};
for (let i = 0, stillflags = true; i < args.length; i++) {
let arg: string | string[] = args[i];
@@ -1257,7 +1296,7 @@ const parseArgs = (
parsedArguments[arg] = values[values.length - 1];
}
}
return [parsedArguments, warnings];
return parsedArguments;
};
export function parseQueryString(
@@ -1352,9 +1391,8 @@ export function parseQueryString(
function buildRequest(
parsedArguments: ParsedArguments,
warnings?: Warnings
warnings: Warnings = []
): Request {
warnings = warnings || [];
// TODO: handle multiple URLs
if (!parsedArguments.url || !parsedArguments.url.length) {
// TODO: better error message (could be parsing fail)
@@ -1699,8 +1737,9 @@ function buildRequest(
function parseCurlCommand(
curlCommand: string | string[],
supportedArgs?: Set<string>
): [Request, Warnings] {
supportedArgs?: Set<string>,
warnings: Warnings = []
): Request {
let cmdName: string,
args: string[],
stdin: undefined | string,
@@ -1711,7 +1750,7 @@ function parseCurlCommand(
throw new CCError("no arguments provided");
}
} else {
({ cmdName, args, stdin, input } = tokenize(curlCommand));
({ cmdName, args, stdin, input } = tokenize(curlCommand, warnings));
if (typeof cmdName === "undefined") {
throw new CCError("failed to parse input");
}
@@ -1732,11 +1771,12 @@ function parseCurlCommand(
}
}
const [parsedArguments, warnings] = parseArgs(
const parsedArguments = parseArgs(
args,
curlLongOpts,
curlShortOpts,
supportedArgs
supportedArgs,
warnings
);
const request = buildRequest(parsedArguments, warnings);
if (stdin) {
@@ -1745,7 +1785,7 @@ function parseCurlCommand(
if (input) {
request.input = input;
}
return [request, warnings];
return request;
}
// Gets the first header, matching case-insensitively

View File

@@ -10,7 +10,7 @@ export const fixturesDir = path.resolve(__dirname, "../../test/fixtures");
// Special case that returns the parsed argument object
const toParser = (curl: string | string[]): string => {
const [parserOutput, warnings] = utils.parseCurlCommand(curl);
const parserOutput = utils.parseCurlCommand(curl);
const code = JSON.stringify(parserOutput, null, 2);
return code + "\n";
};

View File

@@ -129,7 +129,7 @@ const testFile = async (testFilename: string): Promise<void> => {
throw new Error("input file doesn't exist: " + inputFile);
}
const curlCommand = fs.readFileSync(inputFile, "utf8");
const requestedUrl = utils.parseCurlCommand(curlCommand)[0].url;
const requestedUrl = utils.parseCurlCommand(curlCommand).url;
if (!requestedUrl.replace("http://", "").startsWith(EXPECTED_URL)) {
throw new Error(
inputFile +

View File

@@ -11,6 +11,8 @@
"module": "es2022",
"esModuleInterop": true,
"strictNullChecks": true,
"noUnusedParameters": false,
"noUnusedLocals": false,
"noImplicitReturns": true
},
"include": ["src/**/*", "test/**/*", "tools/**/*", "index.d.ts"],