Skip to content

Commit

Permalink
feat(docs): warning for dynamic code blocks (#989)
Browse files Browse the repository at this point in the history
This PR introduces a dynamic warning to code snippets in docs that will
warn whenever code snippets do not match with the same code in `main`.

This also introduces the restriction that code snippets may only be
created with URLs containing a commit hash.

To test this feature, you can add this code to any markdown file (such
as for example in a new file `./docs/site/docs/test.md`) and visit it:

```markdown
# 

import GitHubCodeBlock from '@site/src/components/GitHubCodeBlock/GitHubCodeBlock';

## Out of Date Code

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/97df20bfb1411f30b01d79411cb8b3d0f9213573/contracts/src/pkg/XApp.sol#L60-L69" />

## Code Line Ref Without Commit Hash

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/v0.1.0/contracts/src/pkg/XApp.sol#L60-L69" />
```

task: https://app.asana.com/0/1206208509925075/1207138429056637/f

---------

Co-authored-by: Kevin Halliday <kevin@omni.network>
  • Loading branch information
idea404 and kevinhalliday authored May 8, 2024
1 parent d691e4b commit b21a4ee
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 241 deletions.
6 changes: 3 additions & 3 deletions docs/site/docs/develop/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A reference for Omni's user facing solidity contracts and libraries.
<details>
<summary>`IOmniPortal.sol` Reference Solidity Interface</summary>

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/main/contracts/src/interfaces/IOmniPortal.sol" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/main/contracts/src/interfaces/IOmniPortal.sol" />
</details>

### [`XApp`](https://github.com/omni-network/omni/blob/main/contracts/src/pkg/XApp.sol)
Expand All @@ -29,7 +29,7 @@ A reference for Omni's user facing solidity contracts and libraries.
<details>
<summary>`XApp.sol` Reference Solidity Interface</summary>

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/main/contracts/src/pkg/XApp.sol" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/main/contracts/src/pkg/XApp.sol" />
</details>

### [`XTypes`](https://github.com/omni-network/omni/blob/main/contracts/src/libraries/XTypes.sol)
Expand All @@ -40,5 +40,5 @@ A reference for Omni's user facing solidity contracts and libraries.
<details>
<summary>`XTypes.sol` Reference Solidity Code</summary>

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/main/contracts/src/libraries/XTypes.sol" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/main/contracts/src/libraries/XTypes.sol" />
</details>
2 changes: 1 addition & 1 deletion docs/site/docs/develop/xapp/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Here's an example of a simple cross chain contract, `XGreeter`. This contract le

## `XGreeter` Contract

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni-forge-template/blob/main/src/XGreeter.sol" />
<GitHubCodeBlock url="https://github.com/omni-network/omni-forge-template/blob/main/src/XGreeter.sol" />

## Walkthrough

Expand Down
2 changes: 1 addition & 1 deletion docs/site/docs/develop/xapp/fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ uint256 fee = feeFor(

`XApp` handles calculating and charging fees when making an `xcall`

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/059303647e07fc3481e379b710922e2b84b1827f/contracts/src/pkg/XApp.sol#L56-65" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/059303647e07fc3481e379b710922e2b84b1827f/contracts/src/pkg/XApp.sol#L56-L65" />

`xcall(...)` charges fees to your contract by default. To charge users for fees, calculate the fee with `feeFor(...)`, and verify `msg.value` is sufficient.

Expand Down
4 changes: 2 additions & 2 deletions docs/site/docs/protocol/xmessages/components/portals.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ To initiate a cross-chain call, the Portal Contract provides the `xcall` method.

Below is a summarized fragment for the underlying logic beneath `xcall` from the [Portal contract Solidity source](https://github.com/omni-network/omni/blob/1439d8a99f66a3bb3b7d113c63f8f073512c5377/contracts/src/protocol/OmniPortal.sol):

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/059303647e07fc3481e379b710922e2b84b1827f/contracts/src/protocol/OmniPortal.sol#L135-L151" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/d691e4baced919d86091c7dc3a3ecac7966c8321/contracts/src/protocol/OmniPortal.sol#L135-L151" />

For detailed instructions on conducting cross-chain transactions, refer to the [developer section](../../../develop/introduction.md).

Expand Down Expand Up @@ -71,7 +71,7 @@ To ensure the integrity and authenticity of incoming cross-chain messages, the f

Below is a summarized fragment for the validations in `xsubmit` from the [Portal contract Solidity source](https://github.com/omni-network/omni/blob/main/contracts/src/protocol/OmniPortal.sol):

<GitHubCodeBlock repoUrl="https://github.com/omni-network/omni/blob/059303647e07fc3481e379b710922e2b84b1827f/contracts/src/protocol/OmniPortal.sol#L165-L220" />
<GitHubCodeBlock url="https://github.com/omni-network/omni/blob/059303647e07fc3481e379b710922e2b84b1827f/contracts/src/protocol/OmniPortal.sol#L165-L220" />

</details>

Expand Down
2 changes: 2 additions & 0 deletions docs/site/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const config: Config = {
locales: ["en"],
},

clientModules: ["src/client-modules/index.ts"],

presets: [
[
"classic",
Expand Down
1 change: 1 addition & 0 deletions docs/site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@docusaurus/preset-classic": "3.2.1",
"@docusaurus/theme-common": "^3.2.1",
"@mdx-js/react": "^3.0.0",
"@tanstack/react-query": "^5.35.1",
"axios": "^1.6.8",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
Expand Down
16 changes: 16 additions & 0 deletions docs/site/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions docs/site/src/client-modules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Client module entrypoint
// All modules loaded globally on site
// See https://docusaurus.io/docs/api/docusaurus-config#clientModules

import "./react-query-client"
3 changes: 3 additions & 0 deletions docs/site/src/client-modules/react-query-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { QueryClient } from '@tanstack/react-query'

export const client = new QueryClient()
40 changes: 31 additions & 9 deletions docs/site/src/components/GitHubCodeBlock/GitHubCodeBlock.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
bottom: 0;
right: 0;
margin: 0;
padding: 5px 10px; /* Slightly more padding on the right for visual comfort */
background: var(--ifm-color-secondary); /* Background color */
color: var(--ifm-font-color-base); /* Text color */
border-top-left-radius: 4px; /* adds rounded corners to the left side */
font-size: 0.85em; /* Adjusts the font size */
padding: 5px 10px;
background: var(--ifm-color-secondary);
color: var(--ifm-font-color-base);
border-top-left-radius: 4px;
font-size: 0.85em;
}

.code-snippet-footer a {
Expand All @@ -21,15 +21,37 @@
}

.code-snippet-footer img {
height: 1.4em; /* Ensure consistent size for the icon */
vertical-align: text-top; /* Aligns icon with the text */
margin-left: 5px; /* Space between text and icon */
height: 1.4em;
vertical-align: text-top;
margin-left: 5px;
}

.code-snippet-block {
width: 100%;
}

.code-snippet-footer:hover {
background: var(--ifm-color-secondary-dark); /* Hover background color */
background: var(--ifm-color-secondary-dark);
}

.code-snippet-warning {
position: absolute;
bottom: 0;
left: 0;
margin: 0;
padding: 5px 10px;
background: rgba(182, 161, 25, 0.445);
color: var(--ifm-color-white);
border-top-right-radius: 4px;
font-size: 0.85em;
opacity: 0.9;
}

.code-snippet-warning:hover {
background: rgba(182, 161, 25, 0.797);
}

.code-snippet-warning a {
text-decoration: none;
color: inherit;
}
145 changes: 67 additions & 78 deletions docs/site/src/components/GitHubCodeBlock/GitHubCodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,82 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import CodeBlock from "@theme/CodeBlock";
import { useColorMode } from '@docusaurus/theme-common';
import { useColorMode } from '@docusaurus/theme-common'
import CodeBlock from '@theme/CodeBlock'
import { getNumberOfLines, useCodeBlock } from './useCodeBlock'

import './GitHubCodeBlock.css';
import './GitHubCodeBlock.css'

const GitHubCodeBlock = ({ repoUrl }) => {
const [code, setCode] = useState('');
const [language, setLanguage] = useState('plaintext');
const [sourceUrl, setSourceUrl] = useState('');
const GitHubCodeBlock = ({ url }: { url: string }) => {
const { data, error, isLoading, isError } = useCodeBlock({ url })

useEffect(() => {
async function fetchCode() {
// Use a single regex that can optionally capture line numbers
const regex = /(https:\/\/github\.com\/)([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/([^#]+)#L(\d+)-L(\d+)/;
var match = repoUrl.match(regex);
if (!match) {
const noLinesRegex = /(https:\/\/github\.com\/)([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/([^#]+)/;
match = repoUrl.match(noLinesRegex);
}
if (isLoading) return <CodeBlockLoading numLines={getNumberOfLines(url)}/>
if (isError) return <CodeBlockError url={url} error={error} />

if (match) {
const [wholeUrl, site, owner, repo, branch, filePath, startLine, endLine] = match;
// Build the source URL to view on GitHub, including line numbers if available
setSourceUrl(`https://github.com/${owner}/${repo}/blob/${branch}/${filePath}${startLine ? `#L${startLine}-L${endLine}` : ''}`);
// Fetch the raw content from GitHub
const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${filePath}` + (startLine && endLine ? `#L${startLine}-L${endLine}` : '');
const { code, matchesMain, mainURL, sourceURL, language } = data

try {
const response = await axios.get(rawUrl);
const allLines = response.data.split('\n');
// Apply line slicing only if line numbers are provided and valid
const lines = (startLine && endLine) ? allLines.slice(parseInt(startLine) - 1, parseInt(endLine)).join('\n') : allLines.join('\n');
setCode(lines);
setLanguage(determineLanguage(filePath));
} catch (error) {
console.error('Error fetching code:', error.response ? error.response.data : error.message);
setCode(`Error: ${error.response ? error.response.data : "Could not fetch file"}`);
}
} else {
console.error("Regex match failed. Check the URL format.");
setCode("Error: Invalid GitHub URL format.");
}
}
return (
<div className="code-snippet-container">
<CodeBlock language={language} className="code-snippet-block">
{code}
</CodeBlock>

{!matchesMain && (
<div className="code-snippet-warning">
<a href={mainURL}>
<strong>Warning: </strong>Code shown does not match the main branch.
Please visit the repository URL for actual code.
</a>
</div>
)}

<div className="code-snippet-footer">
<a href={sourceURL}>See source on GitHub </a>
<GithubIcon />
</div>
</div>
)
}

fetchCode();
}, [repoUrl]);
const GithubIcon = () => {
const { colorMode } = useColorMode()
return (
<img
src={
colorMode === 'dark'
? '/img/github-icon-light.svg'
: '/img/github-icon-dark.svg'
}
alt="GitHub"
/>
)
}

function determineLanguage(filePath) {
const extension = filePath.slice(filePath.lastIndexOf('.'));
const languageMap = {
'.js': 'javascript',
'.ts': 'typescript',
'.py': 'python',
'.cpp': 'cpp',
'.c': 'c',
'.java': 'java',
'.rs': 'rust',
'.html': 'html',
'.css': 'css',
'.md': 'markdown',
'.sh': 'bash',
'.sol': 'solidity',
'.go': 'go',
'.json': 'json',
'.yml': 'yaml',
'.yaml': 'yaml',
'.toml': 'toml',
'.xml': 'xml',
'.sql': 'sql',
};
return languageMap[extension] || 'plaintext';
}
const CodeBlockLoading = ({ numLines }: { numLines: number }) => {
const blankLines = Array.from({ length: numLines }, (_, index) => (
<div key={index} className="loading-line" />
));

function getGitHubIcon() {
const { colorMode } = useColorMode();
return colorMode === 'dark' ? "/img/github-icon-light.svg" : "/img/github-icon-dark.svg";
}
return (
<div className="code-snippet-container">
<CodeBlock language="plaintext" className="code-snippet-block">
Fetching code from GitHub...
{blankLines}
</CodeBlock>
</div>
);
};

const CodeBlockError = ({ url, error }: { url: string; error: Error }) => {
console.error('CodeBlockError:', error)
return (
<div className="code-snippet-container">
<CodeBlock language={language} className="code-snippet-block">{code}</CodeBlock>
<CodeBlock language="plaintext" className="code-snippet-block">
Oops :( Something went wrong.
</CodeBlock>
<div className="code-snippet-footer">
<a href={sourceUrl} target="_blank" rel="noopener noreferrer">
See source on GitHub <img src={getGitHubIcon()} alt="GitHub" />
</a>
<a href={url}>Check source on GitHub </a>
<GithubIcon />
</div>
</div>
);
};
)
}

export default GitHubCodeBlock;
export default GitHubCodeBlock
Loading

0 comments on commit b21a4ee

Please sign in to comment.