diff --git a/packages/gatsby-codemods/README.md b/packages/gatsby-codemods/README.md index 5a4324c1a1b05..e7428deffe553 100644 --- a/packages/gatsby-codemods/README.md +++ b/packages/gatsby-codemods/README.md @@ -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 +``` + +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 `` component. +export default props => ( +-
navigateTo(`/`)}>Click to go to home
++
navigate(`/`)}>Click to go to home
+) +``` + ### More scripts Check out [issue 5038 in the Gatsby repo for additional codemod ideas](https://github.com/gatsbyjs/gatsby/issues/5038#issuecomment-411516865). diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.input.js index edd1b8d82a8aa..6f55c379c488d 100644 --- a/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.input.js +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.input.js @@ -1,5 +1,4 @@ /* eslint-disable */ -// TODO: update codemod to make this test pass export const query = graphql` query { allSitePages { diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.output.js index 93634daeec07c..565436588b436 100644 --- a/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.output.js +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/global-graphql-calls/no-import-esm.output.js @@ -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 { diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/.prettierignore b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/.prettierignore new file mode 100644 index 0000000000000..1d085cacc9f8e --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/.prettierignore @@ -0,0 +1 @@ +** diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.input.js new file mode 100644 index 0000000000000..739eb2e95fe63 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.input.js @@ -0,0 +1,7 @@ +/* eslint-disable */ +const React = require('react'); +const { navigateTo } = require('gatsby-link'); + +export default function Example() { + return ; +} diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.output.js new file mode 100644 index 0000000000000..f5fef1c509045 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-commonjs.output.js @@ -0,0 +1,7 @@ +/* eslint-disable */ +const React = require('react'); +const { navigate } = require('gatsby'); + +export default function Example() { + return ; +} diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.input.js new file mode 100644 index 0000000000000..8173f1780b716 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.input.js @@ -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 `` component. +export default props => ( +
navigateTo(`/`)}>Click to go to home
+) diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.output.js new file mode 100644 index 0000000000000..3e561f767e587 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/basic-esm.output.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +import React from "react" +import { navigate } from 'gatsby'; + +// Don't use navigate with an onClick btw :-) +// Generally just use the `` component. +export default props => ( +
navigate(`/`)}>Click to go to home
+) \ No newline at end of file diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.input.js new file mode 100644 index 0000000000000..0d25c4bac2be6 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.input.js @@ -0,0 +1,16 @@ +/* eslint-disable */ +import React from 'react'; +import { graphql } from 'gatsby'; +import { navigateTo } from 'gatsby-link'; + +export default function Example() { + return ; +} + +export const pageQuery = graphql` + query { + allSitePages { + prefix + } + } +`; diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.output.js new file mode 100644 index 0000000000000..81b7f361c9f52 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/existing-esm-import.output.js @@ -0,0 +1,15 @@ +/* eslint-disable */ +import React from 'react'; +import { graphql, navigate } from 'gatsby'; + +export default function Example() { + return ; +} + +export const pageQuery = graphql` + query { + allSitePages { + prefix + } + } +`; diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.input.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.input.js new file mode 100644 index 0000000000000..34699aba5d776 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.input.js @@ -0,0 +1,6 @@ +/* eslint-disable */ +import React from 'react'; + +export default function Hello() { + return

Oh hey

; +} diff --git a/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.output.js b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.output.js new file mode 100644 index 0000000000000..34699aba5d776 --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__testfixtures__/navigate-calls/pass-through.output.js @@ -0,0 +1,6 @@ +/* eslint-disable */ +import React from 'react'; + +export default function Hello() { + return

Oh hey

; +} diff --git a/packages/gatsby-codemods/src/transforms/__tests__/global-graphql-calls-test.js b/packages/gatsby-codemods/src/transforms/__tests__/global-graphql-calls-test.js index 18f21f8647bcf..7a4ff7e474473 100644 --- a/packages/gatsby-codemods/src/transforms/__tests__/global-graphql-calls-test.js +++ b/packages/gatsby-codemods/src/transforms/__tests__/global-graphql-calls-test.js @@ -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`, ] diff --git a/packages/gatsby-codemods/src/transforms/__tests__/navigate-calls-test.js b/packages/gatsby-codemods/src/transforms/__tests__/navigate-calls-test.js new file mode 100644 index 0000000000000..4fd80c25e1e0c --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/__tests__/navigate-calls-test.js @@ -0,0 +1,19 @@ +const tests = [ + `basic-esm`, + `existing-esm-import`, + `basic-commonjs`, + `pass-through`, +] + +const defineTest = require(`jscodeshift/dist/testUtils`).defineTest + +describe(`codemods`, () => { + tests.forEach(test => + defineTest( + __dirname, + `navigate-calls`, + null, + `navigate-calls/${test}` + ) + ) +}) diff --git a/packages/gatsby-codemods/src/transforms/global-graphql-calls.js b/packages/gatsby-codemods/src/transforms/global-graphql-calls.js index 469c1b92b591f..08e0427709db5 100644 --- a/packages/gatsby-codemods/src/transforms/global-graphql-calls.js +++ b/packages/gatsby-codemods/src/transforms/global-graphql-calls.js @@ -32,14 +32,18 @@ function addEsmImport(j, root, tag) { return // already exists if (!existingImport.length) { + const first = root.find(j.Program).get(`body`, 0) + 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 } @@ -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, { + init: { + callee: { name: `require` }, + }, + }).length === 0 if (useImportSyntax) { addEsmImport(j, root, tag) diff --git a/packages/gatsby-codemods/src/transforms/navigate-calls.js b/packages/gatsby-codemods/src/transforms/navigate-calls.js new file mode 100644 index 0000000000000..3f05189d489bc --- /dev/null +++ b/packages/gatsby-codemods/src/transforms/navigate-calls.js @@ -0,0 +1,149 @@ +const EXISTING_METHOD = `navigateTo` +const METHOD = `navigate` +const EXISTING_MODULE_NAME = `gatsby-link` +const MODULE_NAME = `gatsby` + +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, + }, + }) + + const containsNavigateTo = importStatement.find(j.Identifier, { + name: EXISTING_METHOD, + }).length > 0 + + if (!importStatement.length || !containsNavigateTo) { + return + } + + addGatsbyImport(j, root) + replaceGatsbyLinkImport(j, root, importStatement) + replaceCallExpressions(j, 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 + } + + addGatsbyRequire(j, root, requires) + replaceGatsbyLinkRequire(j, root, requires) + replaceCallExpressions(j, 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 + }) +} + +module.exports = (file, api, options) => { + const j = api.jscodeshift + const root = j(file.source) + + const isEsm = root.find(j.ImportDeclaration).length > 0 + + if (isEsm) { + replaceEsm(j, root) + } else { + replaceCommonJs(j, root) + } + + return root.toSource({ quote: `single` }) +}