Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: add a navigateTo -> navigate codemod #7532

Merged
merged 7 commits into from
Sep 4, 2018
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
25 changes: 25 additions & 0 deletions packages/gatsby-codemods/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ export const query = graphql`
`
```

#### `navigate-calls`

Change the deprecated `navigateTo` method from `gatsby-link` to `navigate` from the `gatsby` module.

See the [Gatsby v2 migration guide for details on when to use this](https://next.gatsbyjs.org/docs/migrating-from-v1-to-v2/#change-navigateto-to-navigate).

```sh
jscodeshift -t node_modules/gatsby-codemods/dist/transforms/navigate-calls.js <path>
```

Example result:

```diff
import React from "react"
- import { navigateTo } from "gatsby-link"
+ import { navigate } from "gatsby"

// Don't use navigate with an onClick btw :-)
// Generally just use the `<Link>` component.
export default props => (
- <div onClick={() => navigateTo(`/`)}>Click to go to home</div>
+ <div onClick={() => navigate(`/`)}>Click to go to home</div>
)
```

### More scripts

Check out [issue 5038 in the Gatsby repo for additional codemod ideas](https://github.com/gatsbyjs/gatsby/issues/5038#issuecomment-411516865).
Expand Down
1 change: 1 addition & 0 deletions packages/gatsby-codemods/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"build": "babel src --out-dir . --ignore **/__tests__",
"watch": "babel -w src --out-dir . --ignore **/__tests__",
"test": "jest",
"prepare": "cross-env NODE_ENV=production npm run build"
},
"keywords": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable */
// TODO: update codemod to make this test pass
export const query = graphql`
query {
allSitePages {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable */
// TODO: update codemod to make this test pass
import { graphql } from "gatsby";
import { graphql } from 'gatsby';

export const query = graphql`
query {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */
const React = require('react');
const { navigateTo } = require('gatsby-link');

export default function Example() {
return <button onClick={() => navigateTo('/sample')}>waddup</button>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable */
const React = require('react');
const { navigate } = require('gatsby');

export default function Example() {
return <button onClick={() => navigate('/sample')}>waddup</button>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable */
import React from "react"
import { navigateTo } from "gatsby-link"

// Don't use navigate with an onClick btw :-)
// Generally just use the `<Link>` component.
export default props => (
<div onClick={() => navigateTo(`/`)}>Click to go to home</div >
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable */
import React from "react"
import { navigate } from 'gatsby';

// Don't use navigate with an onClick btw :-)
Copy link
Contributor

Choose a reason for hiding this comment

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

😄

// Generally just use the `<Link>` component.
export default props => (
<div onClick={() => navigate(`/`)}>Click to go to home</div >
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
import React from 'react';
import { graphql } from 'gatsby';
import { navigateTo } from 'gatsby-link';

export default function Example() {
return <button onClick={() => navigateTo('/sample')}>waddup</button>;
}

export const pageQuery = graphql`
query {
allSitePages {
prefix
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable */
import React from 'react';
import { graphql, navigate } from 'gatsby';

export default function Example() {
return <button onClick={() => navigate('/sample')}>waddup</button>;
}

export const pageQuery = graphql`
query {
allSitePages {
prefix
}
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ const tests = [
`import-default`,
`import-named-exports`,
`import-namespace`,
// TODO: update
// `no-import-esm`,
`no-import-esm`,
`require-destructure`,
`require-namespace`,
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const tests = [
`basic-esm`,
`existing-esm-import`,
`basic-commonjs`,
]

const defineTest = require(`jscodeshift/dist/testUtils`).defineTest

describe(`codemods`, () => {
tests.forEach(test =>
defineTest(
__dirname,
`navigate-calls`,
null,
`navigate-calls/${test}`
)
)
})
19 changes: 14 additions & 5 deletions packages/gatsby-codemods/src/transforms/global-graphql-calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@ function addEsmImport(j, root, tag) {
return // already exists

if (!existingImport.length) {
const first = root.find(j.Program).get('body', 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may not actually need this because there will likely be a import React from 'react'; or const React = require('react'); as the first line, correct? If that's the case, I can tweak this a bit and just use insertAfter

const comments = first.node.comments.splice(0)
const importStatement = j.importDeclaration(
[j.importSpecifier(j.identifier(IMPORT_NAME))],
j.literal(MODULE_NAME)
)
importStatement.comments = comments
root
.find(j.Program)
.get(`body`, 0)
.insertBefore(
j.importDeclaration(
[j.importSpecifier(j.identifier(IMPORT_NAME))],
j.literal(MODULE_NAME)
)
importStatement
)
return
}
Expand Down Expand Up @@ -119,7 +123,12 @@ module.exports = (file, api, options) => {
let tag = tags.get(0)

const useImportSyntax =
root.find(j.ImportDeclaration, { importKind: `value` }).length > 0
root.find(j.ImportDeclaration, { importKind: `value` }).length > 0 ||
root.find(j.VariableDeclarator, {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an assumption. If there isn't any import or require statement, we have to make a decision here. I think we should prefer esm, but I'd rather not make any assumption--thus see above where I think we'll always have at least one import/require statement in the file

init: {
callee: { name: `require` },
},
}).length === 0

if (useImportSyntax) {
addEsmImport(j, root, tag)
Expand Down
153 changes: 153 additions & 0 deletions packages/gatsby-codemods/src/transforms/navigate-calls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const EXISTING_METHOD = `navigateTo`
const METHOD = `navigate`
const EXISTING_MODULE_NAME = `gatsby-link`
const MODULE_NAME = `gatsby`

const output = root => root.toSource({ quote: `single` })

const getFirstNode = (j, root) => {
const first = root.find(j.Program).get(`body`, 0)
return [first, first.node]
}

const replaceEsm = (j, root) => {
const importStatement = root.find(j.ImportDeclaration, {
source: {
value: EXISTING_MODULE_NAME,
},
})

if (!importStatement.length || !containsNavigateTo(j, root, importStatement)) {
return false
}

addGatsbyImport(j, root)
replaceGatsbyLinkImport(j, root, importStatement)
replaceCallExpressions(j, root)

return output(root)
}

const replaceCommonJs = (j, root) => {
const requires = root.find(j.VariableDeclarator, {
init: {
callee: { name: `require` },
},
})

const gatsbyLink = requires.find(j.CallExpression, {
arguments: [{ value: EXISTING_MODULE_NAME }],
})

const navigateTo = requires.find(j.Identifier, {
name: EXISTING_METHOD,
})

if (!gatsbyLink.length || !navigateTo.length) {
return false
}

addGatsbyRequire(j, root, requires)
replaceGatsbyLinkRequire(j, root, requires)
replaceCallExpressions(j, root)

return output(root)
}

const addGatsbyImport = (j, root) => {
const gatsbyImport = root.find(j.ImportDeclaration, {
source: {
value: MODULE_NAME,
},
})

if (!gatsbyImport.length) {
const [first] = getFirstNode(j, root)
let statement = j.importDeclaration(
[j.importSpecifier(j.identifier(METHOD))],
j.literal(MODULE_NAME)
)
first
.insertAfter(statement)
return
}

gatsbyImport
.replaceWith(({ node }) => {
node.specifiers = node.specifiers.concat(
j.importSpecifier(j.identifier(METHOD))
)
return node
})
}

// TODO: make work with existing gatsby requires (e.g. `const { StaticQuery } = require('gatsby');`)
const addGatsbyRequire = (j, root, requires) => {
const gatsbyRequire = requires.find(j.CallExpression, {
arguments: [{ value: MODULE_NAME }],
})

if (!gatsbyRequire.length) {
const [first] = getFirstNode(j, root)
let statement = j.template.statement([
`const { ${METHOD} } = require('${MODULE_NAME}');\n`,
])
first
.insertAfter(statement)
return
}
}

const replaceGatsbyLinkImport = (j, root, importStatement) => {
const imports = importStatement.find(j.Identifier)

const allExistingMethods = imports.every(node => node.value.name === EXISTING_METHOD)

if (allExistingMethods) {
importStatement.remove()
}
}

const replaceGatsbyLinkRequire = (j, root, requires) => {
const links = requires.filter(el => j(el).find(j.CallExpression, {
arguments: [{ value: EXISTING_MODULE_NAME }],
}).length > 0)

links.forEach(el => {
const node = j(el)

node.remove()
})
}

const replaceCallExpressions = (j, root) => {
const expressions = root.find(j.CallExpression, {
callee: { name: EXISTING_METHOD },
})

if (!expressions.length) {
return
}

expressions.replaceWith(({ node }) => {
node.callee.name = METHOD
return node
})
}

const containsNavigateTo = (j, root, importStatement) => importStatement.find(j.Identifier, {
name: EXISTING_METHOD,
}).length > 0

module.exports = (file, api, options) => {
const j = api.jscodeshift
const root = j(file.source)

const isEsm = root.find(j.ImportDeclaration).length > 0

if (isEsm) {
return replaceEsm(j, root)
}

return replaceCommonJs(j, root)
}