Skip to content

Commit 3516d4c

Browse files
committed
add react/prevent-default snippet
1 parent 6ba4f4f commit 3516d4c

File tree

5 files changed

+190
-2
lines changed

5 files changed

+190
-2
lines changed

lib/helpers/find-react-component.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
new Synvert.Helper('find-react-component', function(options, helperFn) {
2+
let componentName = null;
3+
withNode('.ExportAssignment[expression=.Identifier]', function () {
4+
componentName = this.currentNode.expression.escapedText;
5+
});
6+
withNode(`.VariableDeclaration[name=${componentName}][initializer=.ArrowFunction]`, function () {
7+
helperFn();
8+
});
9+
});

lib/react/prevent-default.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
function capitalizeFirstLetter(string) {
2+
return string.charAt(0).toUpperCase() + string.slice(1);
3+
}
4+
5+
new Synvert.Rewriter("react", "prevent-default", () => {
6+
description(`
7+
convert foo to bar
8+
`);
9+
10+
configure({ parser: Synvert.Parser.TYPESCRIPT });
11+
12+
withinFiles(Synvert.ALL_FILES, function () {
13+
callHelper('helpers/find-react-component', () => {
14+
// { functionName: 'functionNotDefined' } the function is not defined;
15+
// { functionName: 'functionNoEventParameter' } the function is defined but not event parameter;
16+
// { functionName: 'preventDefaultNotCalled' } the function is defined with event parameter but not call preventDefault();
17+
// { functionName: 'preventDefaultCalled' } the function is defined with event parameter and call preventDefault();
18+
const onClickPreventDefault = {};
19+
20+
// find a elements whose href attribute is '#'
21+
findNode(`
22+
.JsxElement
23+
[openingElement=.JsxOpeningElement
24+
[tagName=a]
25+
[attributes=.JsxAttributes
26+
[properties includes .JsxAttribute
27+
[name=href]
28+
[initializer=.StringLiteral[text="#"]]
29+
]
30+
]
31+
]
32+
[closingElement=.JsxClosingElement[tagName=a]]`,
33+
() => {
34+
findNode(`.JsxAttribute[name=onClick]`, () => {
35+
onClickPreventDefault[this.currentNode.initializer.expression.escapedText] = 'functionNotDefined';
36+
});
37+
}
38+
);
39+
40+
for (const functionName of Object.keys(onClickPreventDefault)) {
41+
findNode(`.VariableDeclaration[name=${functionName}][initializer=.ArrowFunction]`, () => {
42+
if (this.currentNode.initializer.parameters.length === 0) {
43+
// no parameters
44+
onClickPreventDefault[functionName] = 'functionNoEventParameter';
45+
} else {
46+
// has parameters
47+
onClickPreventDefault[functionName] = 'preventDefaultNotCalled';
48+
const eventVariable = this.currentNode.initializer.parameters[0].name.escapedText;
49+
findNode(`.CallExpression[expression=.PropertyAccessExpression[name=preventDefault][expression=${eventVariable}]]`, () => {
50+
onClickPreventDefault[functionName] = 'preventDefaultCalled';
51+
});
52+
}
53+
});
54+
}
55+
56+
for (const [functionName, reason] of Object.entries(onClickPreventDefault)) {
57+
if (reason === 'functionNotDefined') {
58+
findNode(`
59+
.JsxElement
60+
[openingElement=.JsxOpeningElement
61+
[tagName=a]
62+
[attributes=.JsxAttributes
63+
[properties includes .JsxAttribute
64+
[name=href]
65+
[initializer=.StringLiteral[text="#"]]
66+
]
67+
]
68+
]
69+
[closingElement=.JsxClosingElement[tagName=a]] .JsxAttribute[name=onClick][initializer=.JsxExpression[expression=${functionName}]]`,
70+
() => {
71+
replace('initializer', { with: `{on${capitalizeFirstLetter(functionName)}}` });
72+
}
73+
);
74+
75+
findNode(`.ReturnStatement`, () => {
76+
insertBefore(`
77+
const on${capitalizeFirstLetter(functionName)} = (event) => {
78+
event.preventDefault();
79+
80+
${functionName}();
81+
}
82+
`.trim() + "\n", { fixIndent: true });
83+
});
84+
}
85+
86+
if (reason === 'functionNoEventParameter') {
87+
findNode(`.VariableDeclaration[name=${functionName}][initializer=.ArrowFunction]`, () => {
88+
replace('initializer.parameters', { with: '(event)' });
89+
prepend("event.preventDefault();\n");
90+
});
91+
}
92+
}
93+
});
94+
});
95+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const helper = "helpers/find-react-component";
2+
const { assertHelper } = require("../utils");
3+
4+
describe("helpers/find-react-component", () => {
5+
const source = `
6+
const MyComponent = () => {
7+
return <div>My Component</div>;
8+
};
9+
10+
export default MyComponent;
11+
`;
12+
assertHelper({
13+
input: source,
14+
output: source,
15+
helper,
16+
options: {},
17+
helperFn: function () {
18+
expect(this.currentNode.name.escapedText).toEqual("MyComponent");
19+
},
20+
});
21+
});

test/react/prevent-default.spec.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const snippet = "react/prevent-default";
2+
const { assertConvert } = require("../utils");
3+
4+
describe(snippet, () => {
5+
describe('add a function to prevent default', () => {
6+
const input = `
7+
const Post = ({ newPost, editPost, doDelete }) => {
8+
const onEditPost = (event) => {
9+
event.preventDefault();
10+
11+
editPost();
12+
}
13+
14+
const deletePost = () => {
15+
doDelete();
16+
}
17+
18+
return (
19+
<a href="#" onClick={newPost}>New</a>
20+
<a href="#" onClick={onEditPost}>Edit</a>
21+
<a href="#" onClick={deletePost}>Delete</a>
22+
)
23+
}
24+
export default Post;
25+
`;
26+
const output = `
27+
const Post = ({ newPost, editPost, doDelete }) => {
28+
const onEditPost = (event) => {
29+
event.preventDefault();
30+
31+
editPost();
32+
}
33+
34+
const deletePost = (event) => {
35+
event.preventDefault();
36+
37+
doDelete();
38+
}
39+
40+
const onNewPost = (event) => {
41+
event.preventDefault();
42+
43+
newPost();
44+
}
45+
46+
return (
47+
<a href="#" onClick={onNewPost}>New</a>
48+
<a href="#" onClick={onEditPost}>Edit</a>
49+
<a href="#" onClick={deletePost}>Delete</a>
50+
)
51+
}
52+
export default Post;
53+
`;
54+
55+
assertConvert({
56+
input,
57+
output,
58+
snippet,
59+
path: "code.jsx",
60+
helpers: ["helpers/find-react-component"],
61+
});
62+
});
63+
});

test/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ const assertHelper = (options) => {
113113
const rewriter = new Synvert.Rewriter("group", "name", function () {
114114
this.configure({ parser: Synvert.Parser.TYPESCRIPT });
115115
this.withinFilesSync("*.{js,jsx}", function () {
116-
this.callHelperSync(options.helper, options.options);
116+
this.callHelperSync(options.helper, options.options, options.helperFn.bind(this));
117117
});
118118
});
119119
rewriter.processSync();
@@ -137,7 +137,7 @@ const assertHelper = (options) => {
137137
const rewriter = new Synvert.Rewriter("group", "name", async function () {
138138
this.configure({ parser: Synvert.Parser.TYPESCRIPT });
139139
await this.withinFiles("*.{js,jsx}", async function () {
140-
await this.callHelper(options.helper, options.options);
140+
await this.callHelper(options.helper, options.options, options.helperFn.bind(this));
141141
});
142142
});
143143
await rewriter.process();

0 commit comments

Comments
 (0)