Skip to content

Commit

Permalink
fix: add better handling for unknown options
Browse files Browse the repository at this point in the history
  • Loading branch information
43081j committed Aug 20, 2024
1 parent c592bda commit 7f38e13
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 36 deletions.
174 changes: 151 additions & 23 deletions codemods/qs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const qsLikeOptionsStr = JSON.stringify(qsLikeOptions);

/**
* @param {SgNode} obj
* @return {Record<string, SgNode>}
*/
function parseOptions(obj) {
/** @type {Record<string, SgNode>} */
Expand All @@ -33,6 +34,97 @@ function parseOptions(obj) {
return result;
}

/** @typedef {Record<string, unknown> | ((value: SgNode) => Record<string, unknown>|null) | null} ReplacementOptions */
/** @typedef {({kind: string; options: ReplacementOptions})} Replacement */
/** @typedef {({replacements: Replacement[]})} Replacer */
/** @type {Record<string, Replacer>} */
const replacements = {
indices: {
replacements: [
{
kind: 'false',
options: {
nestingSyntax: 'dot',
arrayRepeatSyntax: 'repeat',
},
},
{
kind: 'true',
options: {
nestingSyntax: 'js',
},
},
],
},
arrayFormat: {
replacements: [
{
kind: 'string',
options: (value) => {
const formatStr = value.child(1)?.text();
if (formatStr === 'repeat') {
return { arrayRepeatSyntax: 'repeat' };
} else if (formatStr === 'indices') {
return { arrayRepeat: false };
}
return { arrayRepeatSyntax: 'bracket' };
},
},
],
},
allowDots: {
replacements: [
{
kind: 'true',
options: {
nestingSyntax: 'js',
},
},
{
kind: 'false',
options: {
nestingSyntax: 'index',
},
},
],
},
parseArrays: {
replacements: [
{
kind: 'false',
options: {
arrayRepeat: false,
},
},
{
kind: 'true',
options: {
arrayRepeat: true,
},
},
],
},
delimiter: {
replacements: [
{
kind: 'string',
options: (value) => {
const delimiter = value.child(1)?.text();
if (delimiter) {
return { delimiter };
}
console.warn(
`Warning: encountered a delimiter we could not ` +
`transform. It will be dropped, so may need additional fixes ` +
`after this codemod executes`,
);
return null;
},
},
],
},
};

/**
* @param {CodemodOptions} [options]
* @returns {Codemod}
Expand Down Expand Up @@ -118,7 +210,7 @@ export default function (options) {
continue;
}

edits.push(func.replace('pq'));
let decodeResult = false;

if (args.length === 1) {
edits.push(args[0].replace(`${args[0].text()}, ${qsLikeOptionsStr}`));
Expand All @@ -128,37 +220,73 @@ export default function (options) {
/** @type {Record<string, unknown>} */
const newOptions = { ...qsLikeOptions };

if (opts.indices && opts.indices.kind() === 'false') {
newOptions.nestingSyntax = 'dot';
newOptions.arrayRepeatSyntax = 'repeat';
}

if (opts.arrayFormat && opts.arrayFormat.kind() === 'string') {
const arrayFormat = opts.arrayFormat.child(1)?.text();
if (arrayFormat === 'repeat') {
newOptions.arrayRepeatSyntax = 'repeat';
} else if (arrayFormat === 'indices') {
newOptions.arrayRepeat = false;
for (const [key, val] of Object.entries(opts)) {
// Special case for the `encode` option if it is `false`, as we
// need to wrap the entire result with `decodeURIComponent`
if (key === 'encode' && val.kind() === 'false') {
decodeResult = true;
continue;
}
}

if (opts.allowDots && opts.allowDots.kind() === 'true') {
newOptions.nestingSyntax = 'dot';
}
const replacer = replacements[key];

if (opts.parseArrays && opts.parseArrays.kind() === 'false') {
newOptions.arrayRepeat = false;
}
if (!replacer) {
console.warn(
`Warning: encountered an unknown option. ` +
`The option ("${key}") will be dropped, so may need ` +
`additional fixes after this codemod executes.`,
);
continue;
}

if (opts.delimiter && opts.delimiter.kind() === 'string') {
const delimiter = opts.delimiter.child(1)?.text();
if (delimiter) {
newOptions.delimiter = delimiter;
let foundReplacement = false;
for (const replacement of replacer.replacements) {
if (replacement.kind === val.kind()) {
const replacementOpts =
typeof replacement.options === 'function'
? replacement.options(val)
: replacement.options;
foundReplacement = true;
if (replacementOpts) {
for (const optKey in replacementOpts) {
newOptions[optKey] = replacementOpts[optKey];
}
}
}
}

if (!foundReplacement) {
console.warn(
`Warning: encountered an option with a value we could not parse. ` +
`The option ("${key}") has a computed value or an unexpected ` +
`type. It will be dropped, so may need additional fixes ` +
`after this codemod executes.`,
);
}
}

edits.push(args[2].replace(JSON.stringify(newOptions)));
}

if (decodeResult) {
console.warn(
`Warning: the "encode: false" option will be ` +
`replaced by a call to decodeURIComponent`,
);
edits.push(func.replace('decodeURIComponent(pq'));

const argsChildren = expr.field('arguments')?.children();

if (argsChildren) {
const lastArgsChild = argsChildren[argsChildren.length - 1];

if (lastArgsChild.kind() === ')') {
edits.push(lastArgsChild.replace(`${lastArgsChild.text()})`));
}
}
} else {
edits.push(func.replace('pq'));
}
}

return root.commitEdits(edits);
Expand Down
23 changes: 19 additions & 4 deletions test/fixtures/qs/basic/after.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import pq from 'picoquery';
const obj = {foo: 'bar'};
const query = 'foo=bar';

// encode: false, indices: false
// indices: false
pq.stringify(obj, {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"repeat"});

// defaults
Expand All @@ -18,17 +18,32 @@ pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":false,"arra
// arrayFormat: brackets
pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// encode: false, indices: false
// arrayFormat: nonsense defaults to bracket
pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// encode: false
decodeURIComponent(pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"}));

// indices: false
pq.parse(query, {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"repeat"});

// indices: true
pq.parse(query, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// defaults
pq.parse(query, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// delimiter
pq.parse('a=foo;b=bar', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket","delimiter":";"});

// allowDots
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});
// allowDots: true
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// allowDots: false
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"index","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// parseArrays: false
pq.parse('a[]=1&a[]=2', {"nesting":true,"nestingSyntax":"js","arrayRepeat":false,"arrayRepeatSyntax":"bracket"});

// parseArrays: true
pq.parse('a[]=1&a[]=2', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});
25 changes: 20 additions & 5 deletions test/fixtures/qs/basic/before.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import qs from 'qs';
const obj = {foo: 'bar'};
const query = 'foo=bar';

// encode: false, indices: false
qs.stringify(obj, { encode: false, indices: false });
// indices: false
qs.stringify(obj, {indices: false});

// defaults
qs.stringify(obj);
Expand All @@ -18,17 +18,32 @@ qs.stringify(obj, {arrayFormat: 'indices'});
// arrayFormat: brackets
qs.stringify(obj, {arrayFormat: 'brackets'});

// encode: false, indices: false
qs.parse(query, { encode: false, indices: false });
// arrayFormat: nonsense defaults to bracket
qs.stringify(obj, {arrayFormat: 'absolute gibberish'});

// encode: false
qs.stringify(obj, {encode: false});

// indices: false
qs.parse(query, { indices: false });

// indices: true
qs.parse(query, { indices: true });

// defaults
qs.parse(query);

// delimiter
qs.parse('a=foo;b=bar', {delimiter: ';'});

// allowDots
// allowDots: true
qs.parse('a.b=c', {allowDots: true});

// allowDots: false
qs.parse('a.b=c', {allowDots: false});

// parseArrays: false
qs.parse('a[]=1&a[]=2', {parseArrays: false});

// parseArrays: true
qs.parse('a[]=1&a[]=2', {parseArrays: true});
23 changes: 19 additions & 4 deletions test/fixtures/qs/basic/result.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import pq from 'picoquery';
const obj = {foo: 'bar'};
const query = 'foo=bar';

// encode: false, indices: false
// indices: false
pq.stringify(obj, {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"repeat"});

// defaults
Expand All @@ -18,17 +18,32 @@ pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":false,"arra
// arrayFormat: brackets
pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// encode: false, indices: false
// arrayFormat: nonsense defaults to bracket
pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// encode: false
decodeURIComponent(pq.stringify(obj, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"}));

// indices: false
pq.parse(query, {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"repeat"});

// indices: true
pq.parse(query, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// defaults
pq.parse(query, {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// delimiter
pq.parse('a=foo;b=bar', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket","delimiter":";"});

// allowDots
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"dot","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});
// allowDots: true
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// allowDots: false
pq.parse('a.b=c', {"nesting":true,"nestingSyntax":"index","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

// parseArrays: false
pq.parse('a[]=1&a[]=2', {"nesting":true,"nestingSyntax":"js","arrayRepeat":false,"arrayRepeatSyntax":"bracket"});

// parseArrays: true
pq.parse('a[]=1&a[]=2', {"nesting":true,"nestingSyntax":"js","arrayRepeat":true,"arrayRepeatSyntax":"bracket"});

0 comments on commit 7f38e13

Please sign in to comment.