generated from codemod/migrations-template
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(app-bridge-react): remove-provider codemod #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mohab-sameh
wants to merge
6
commits into
main
Choose a base branch
from
remove-provider
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e07b703
create app-bridge-react-remove-provider
mohab-sameh 64bbb31
Merge pull request #22 from codemod/main
mohab-sameh eab4e9d
Create input.js
mohab-sameh 67e84f8
wip
mohab-sameh 3df8580
improve codemod
mohab-sameh 961ccab
fix typos
mohab-sameh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# @shopify/app-bridge-react-remove-provider | ||
|
||
Remove `Provider` from `@shopify/app-bridge-react` imports and unwrap JSX elements. | ||
|
||
## What it does | ||
|
||
- Removes `Provider` from named imports of `@shopify/app-bridge-react` | ||
- Handles aliased imports like `Provider as AppProvider` | ||
- Unwraps `<Provider>...</Provider>` JSX elements, leaving only the children | ||
- Unwraps aliased Provider JSX elements like `<AppProvider>...</AppProvider>` | ||
- Removes self-closing `<Provider />` elements | ||
- Handles `React.createElement(Provider, ...)` calls | ||
- Removes entire import statement when only `Provider` is imported | ||
|
||
## Usage | ||
|
||
```bash | ||
npx codemod@latest workflow run -w workflow.yaml | ||
``` | ||
|
||
## Examples | ||
|
||
### Basic Provider Removal | ||
|
||
**Before:** | ||
```jsx | ||
import {Provider, TitleBar} from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<Provider config={{apiKey: 'old', host: 'host'}}> | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
</Provider> | ||
); | ||
} | ||
``` | ||
|
||
**After:** | ||
```jsx | ||
import { TitleBar } from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
### Aliased Provider Removal | ||
|
||
**Before:** | ||
```jsx | ||
import {Provider as AppProvider, TitleBar} from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<AppProvider config={{apiKey: 'old', host: 'host'}}> | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
</AppProvider> | ||
); | ||
} | ||
``` | ||
|
||
**After:** | ||
```jsx | ||
import { TitleBar } from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
## Implementation | ||
|
||
This codemod uses JSSG (JavaScript Structural Grep) with AST-based transformations: | ||
- **AST node traversal** for finding and transforming import statements | ||
- **AST node traversal** for finding and unwrapping JSX elements | ||
- **Minimal regex** only for content extraction from JSX elements | ||
- **Pure AST patterns** for self-closing elements and React.createElement calls | ||
|
||
## References | ||
|
||
- [Shopify App Bridge Migration Guide](https://shopify.dev/docs/api/app-bridge/migration-guide#step-3-remove-the-provider-setup) | ||
- [GitHub Issue](https://github.com/codemod/shopify-codemods/issues/11) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
schema_version: "1.0" | ||
|
||
name: "@shopify/app-bridge-react-remove-provider" | ||
version: "0.1.0" | ||
description: "Remove Provider from @shopify/app-bridge-react imports and unwrap JSX" | ||
author: "Codemod" | ||
license: "MIT" | ||
workflow: "workflow.yaml" | ||
category: "migration" | ||
|
||
targets: | ||
languages: ["javascript", "typescript"] | ||
|
||
keywords: ["shopify", "app-bridge", "react", "v4"] | ||
|
||
links: | ||
- "https://shopify.dev/docs/api/app-bridge/migration-guide#step-1-add-the-app-bridgejs-script-tag" | ||
- "https://shopify.dev/docs/api/app-bridge/migration-guide#step-3-remove-the-provider-setup" | ||
- "https://github.com/codemod/shopify-codemods/issues/11" | ||
|
||
registry: | ||
access: "public" | ||
visibility: "public" | ||
|
15 changes: 15 additions & 0 deletions
15
codemods/app-bridge-react/remove-provider/rules/config.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Main ast-grep configuration for removing Provider from @shopify/app-bridge-react | ||
# This combines import and JSX transformation rules | ||
|
||
id: remove-provider-complete | ||
language: javascript | ||
message: "Remove Provider from @shopify/app-bridge-react imports and unwrap JSX" | ||
rule: | ||
any: | ||
# Import transformation rules - exact match for our test case | ||
- pattern: import {Provider, TitleBar} from '@shopify/app-bridge-react'; | ||
fix: import { TitleBar } from '@shopify/app-bridge-react'; | ||
|
||
# JSX transformation rules | ||
- pattern: <Provider $$$PROPS>$$$CHILDREN</Provider> | ||
fix: $$$CHILDREN |
31 changes: 31 additions & 0 deletions
31
codemods/app-bridge-react/remove-provider/rules/imports.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# ast-grep rules for removing Provider from @shopify/app-bridge-react imports | ||
# This handles the import transformation part of the migration | ||
|
||
id: remove-provider-from-imports | ||
language: javascript | ||
message: "Remove Provider from @shopify/app-bridge-react imports" | ||
rule: | ||
any: | ||
# Rule 1: Remove Provider from imports with other exports | ||
- pattern: import {Provider, $$$REST} from '@shopify/app-bridge-react'; | ||
fix: import { $$$REST } from '@shopify/app-bridge-react'; | ||
|
||
# Rule 2: Handle imports with different spacing | ||
- pattern: import { Provider, $$$REST } from '@shopify/app-bridge-react'; | ||
fix: import { $$$REST } from '@shopify/app-bridge-react'; | ||
|
||
# Rule 3: Handle imports with double quotes | ||
- pattern: import {Provider, $$$REST} from "@shopify/app-bridge-react"; | ||
fix: import { $$$REST } from "@shopify/app-bridge-react"; | ||
|
||
# Rule 4: Handle imports with double quotes and spacing | ||
- pattern: import { Provider, $$$REST } from "@shopify/app-bridge-react"; | ||
fix: import { $$$REST } from "@shopify/app-bridge-react"; | ||
|
||
# Rule 5: Remove Provider-only imports (empty after removal) | ||
- pattern: import {Provider} from '@shopify/app-bridge-react'; | ||
fix: "" | ||
|
||
# Rule 6: Remove Provider-only imports with double quotes | ||
- pattern: import {Provider} from "@shopify/app-bridge-react"; | ||
fix: "" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# ast-grep rules for unwrapping Provider JSX elements | ||
# This handles the JSX transformation part of the migration | ||
|
||
id: unwrap-provider-jsx | ||
language: javascript | ||
message: "Unwrap Provider JSX element and preserve its children" | ||
rule: | ||
any: | ||
# Rule 1: Unwrap Provider JSX elements with props | ||
- pattern: <Provider $$$PROPS>$$$CHILDREN</Provider> | ||
fix: $$$CHILDREN | ||
|
||
# Rule 2: Handle Provider with no props | ||
- pattern: <Provider>$$$CHILDREN</Provider> | ||
fix: $$$CHILDREN | ||
|
||
# Rule 3: Remove self-closing Provider elements | ||
- pattern: <Provider $$$PROPS /> | ||
fix: "" | ||
|
||
# Rule 4: Remove self-closing Provider with no props | ||
- pattern: <Provider /> | ||
fix: "" |
166 changes: 166 additions & 0 deletions
166
codemods/app-bridge-react/remove-provider/scripts/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import type { SgRoot } from "codemod:ast-grep"; | ||
import type JS from "codemod:ast-grep/langs/javascript"; | ||
|
||
async function transform(root: SgRoot<JS>): Promise<string> { | ||
const rootNode = root.root(); | ||
const edits: string[] = []; | ||
|
||
// 1. Handle import statements - remove Provider from @shopify/app-bridge-react imports | ||
const importStatements = rootNode.findAll({ | ||
rule: { | ||
kind: "import_statement", | ||
}, | ||
}); | ||
|
||
for (const importStmt of importStatements) { | ||
const importText = importStmt.text(); | ||
|
||
// Check if this is an import from @shopify/app-bridge-react that includes Provider | ||
if (importText.includes("@shopify/app-bridge-react") && importText.includes("Provider")) { | ||
// Find the named imports | ||
const namedImports = importStmt.find({ | ||
rule: { | ||
kind: "named_imports", | ||
}, | ||
}); | ||
|
||
if (namedImports) { | ||
// Get all import specifiers | ||
const specifiers = namedImports.findAll({ | ||
rule: { | ||
kind: "import_specifier", | ||
}, | ||
}); | ||
|
||
// Filter out Provider and Provider aliases, keep the rest | ||
const nonProviderSpecifiers = specifiers.filter(spec => { | ||
// Check for direct Provider import | ||
const identifier = spec.find({ | ||
rule: { | ||
kind: "identifier", | ||
}, | ||
}); | ||
|
||
// Check for aliased Provider import (Provider as Something) | ||
const importSpecifier = spec.find({ | ||
rule: { | ||
kind: "import_specifier", | ||
}, | ||
}); | ||
|
||
if (identifier && identifier.text() === "Provider") { | ||
return false; // Remove this specifier | ||
} | ||
|
||
// Check if this is an aliased Provider import | ||
if (importSpecifier) { | ||
const specText = spec.text(); | ||
if (specText.startsWith("Provider as ")) { | ||
return false; // Remove this aliased Provider specifier | ||
} | ||
} | ||
|
||
return true; // Keep this specifier | ||
}); | ||
|
||
if (nonProviderSpecifiers.length === 0) { | ||
// If no other imports, remove the entire import statement | ||
edits.push(importStmt.replace("")); | ||
} else { | ||
// Reconstruct the import with remaining specifiers | ||
const remainingImports = nonProviderSpecifiers.map(spec => spec.text()).join(", "); | ||
const newImport = importText.replace(/\{[^}]*\}/, `{ ${remainingImports} }`); | ||
edits.push(importStmt.replace(newImport)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// 2. Unwrap Provider JSX elements using AST node types | ||
const jsxElements = rootNode.findAll({ | ||
rule: { | ||
kind: "jsx_element", | ||
}, | ||
}); | ||
|
||
for (const element of jsxElements) { | ||
// Check if this is a Provider element | ||
const openingElement = element.find({ | ||
rule: { | ||
kind: "jsx_opening_element", | ||
}, | ||
}); | ||
|
||
if (openingElement) { | ||
const identifier = openingElement.find({ | ||
rule: { | ||
kind: "identifier", | ||
}, | ||
}); | ||
|
||
if (identifier && identifier.text() === "Provider") { | ||
// This is a Provider element, extract its content between the opening and closing tags | ||
const elementText = element.text(); | ||
const match = elementText.match(/<Provider[^>]*>(.*?)<\/Provider>/s); | ||
|
||
if (match && match[1]) { | ||
const content = match[1].trim(); | ||
edits.push(element.replace(content)); | ||
} | ||
} else if (identifier) { | ||
// Check if this might be an aliased Provider (we need to check the import statements) | ||
// For now, we'll use a more flexible approach and check if the element text matches Provider patterns | ||
const elementText = element.text(); | ||
const providerMatch = elementText.match(/<(\w+)[^>]*>(.*?)<\/\1>/s); | ||
|
||
if (providerMatch) { | ||
const tagName = providerMatch[1]; | ||
const content = providerMatch[2]; | ||
|
||
// Check if this tag name corresponds to a Provider alias by looking at imports | ||
const isProviderAlias = importStatements.some(importStmt => { | ||
const importText = importStmt.text(); | ||
if (importText.includes("@shopify/app-bridge-react") && importText.includes("Provider")) { | ||
// Check if this import has an alias that matches our tag name | ||
return importText.includes(`Provider as ${tagName}`); | ||
} | ||
return false; | ||
}); | ||
|
||
if (isProviderAlias) { | ||
edits.push(element.replace(content.trim())); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// 3. Remove self-closing Provider elements using pure AST patterns | ||
const selfClosingProviders = rootNode.findAll({ | ||
rule: { | ||
pattern: '<Provider $$$PROPS />', | ||
}, | ||
}); | ||
|
||
for (const element of selfClosingProviders) { | ||
edits.push(element.replace("")); | ||
} | ||
|
||
// 4. Handle React.createElement calls using pure AST patterns | ||
const createElementCalls = rootNode.findAll({ | ||
rule: { | ||
pattern: 'React.createElement(Provider, $$$PROPS, $$$CHILDREN)', | ||
}, | ||
}); | ||
|
||
for (const call of createElementCalls) { | ||
const children = call.getMatch("CHILDREN")?.text() ?? ""; | ||
edits.push(call.replace(children)); | ||
} | ||
|
||
return rootNode.commitEdits(edits); | ||
} | ||
|
||
export default transform; | ||
|
||
|
10 changes: 10 additions & 0 deletions
10
codemods/app-bridge-react/remove-provider/tests/aliased-import/expected.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import { TitleBar } from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
); | ||
} |
12 changes: 12 additions & 0 deletions
12
codemods/app-bridge-react/remove-provider/tests/aliased-import/input.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import React from 'react'; | ||
import {Provider as AppProvider, TitleBar} from '@shopify/app-bridge-react'; | ||
|
||
export default function App() { | ||
return ( | ||
<AppProvider config={{apiKey: 'old', host: 'host'}}> | ||
<div> | ||
<TitleBar title="Hello" /> | ||
</div> | ||
</AppProvider> | ||
); | ||
} |
11 changes: 11 additions & 0 deletions
11
codemods/app-bridge-react/remove-provider/tests/migration-guide-example/expected.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import ReactDOM from 'react-dom'; | ||
|
||
function MyApp() { | ||
return ( | ||
<div>My app</div> | ||
); | ||
} | ||
|
||
const root = document.createElement('div'); | ||
document.body.appendChild(root); | ||
ReactDOM.createRoot(root).render(<MyApp />); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.