├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── __snapshots__ └── parser.test.js.snap ├── cli.js ├── docs-source ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── attina.js │ ├── editor.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js └── yarn.lock ├── docs ├── asset-manifest.json ├── favicon.ico ├── index.html ├── manifest.json ├── precache-manifest.0f46b7a65bc3f0cdb2f6bf6e214ef341.js ├── service-worker.js └── static │ ├── css │ ├── main.4fd1df1e.chunk.css │ └── main.4fd1df1e.chunk.css.map │ └── js │ ├── 1.b775380a.chunk.js │ ├── 1.b775380a.chunk.js.map │ ├── main.96947aa3.chunk.js │ ├── main.96947aa3.chunk.js.map │ ├── runtime~main.367d1e08.js │ └── runtime~main.367d1e08.js.map ├── example.1.js ├── example.js ├── index.js ├── index.test.js ├── mermaid-diagram-example.svg ├── package-lock.json ├── package.json ├── parser.test.js ├── transform.js └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [crubier] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # code-to-graph 2 | 3 | Transforms code (JS) into graphs (graphviz, mermaid flowchart, ...) 4 | 5 | ## Interactive Demo 6 | 7 | https://crubier.github.io/code-to-graph/ 8 | 9 | [A bigger example](https://crubier.github.io/code-to-graph/?code=aW1wb3J0IHsgdG90byB9IGZyb20gInR1dHUiOwoKZnVuY3Rpb24gdHJ5Q2F0Y2hFeGFtcGxlKGFyZ3MpIHsKICBmdW5jdGlvbiBmKCkgewogICAgY29uc3QgZCA9IGFzeW5jICgpID0-IHsKICAgICAgYXdhaXQgNTsKICAgICAgcmV0dXJuIHRvdG87CiAgICB9OwogICAgY29uc3QgZmYgPSA0NTsKICAgIHRyeSB7CiAgICAgIGxldCBhID0gNTsKICAgICAgYSA9IGEgKyAxOwogICAgICBpZiAoYSA-IDMpIHsKICAgICAgICB0aHJvdyBuZXcgRXJyb3IoIm5vdCBub3JtYWwiKTsKICAgICAgfQogICAgfSBjYXRjaCAoZSkgewogICAgICBjb25zb2xlLmxvZygiaW4gdGhlIGNhdGNoIik7CiAgICAgIGlmICh2KSByZXR1cm4gNTsKICAgICAgaWYgKGZ0dykgdGhyb3cgbmV3IEVycm9yKCJSZWFsIGJhZCIpOwogICAgfSBmaW5hbGx5IHsKICAgICAgY29uc29sZS5sb2coImluIHRoZSBmaW5hbGx5Iik7CiAgICAgIGlmICh1KSByZXR1cm4gODsKICAgIH0KICAgIHJldHVybiAxOwogIH0KfQoKZXhwb3J0IGFzeW5jIGZ1bmN0aW9uKiBnZW5lcmF0b3JFeGFtcGxlKGFyZ3MpIHsKICB5aWVsZCAxOwogIHlpZWxkIDU7CiAgaWYgKG15Q29uZCkgewogICAgY29uc3QgYyA9IHlpZWxkIDIxOwogICAgY29uc3QgZCA9IGF3YWl0IGNvY28oYyk7CiAgfSBlbHNlIHsKICAgIHlpZWxkIDI1OwogICAgbGV0IHUgPSB5aWVsZCBmKDQ1ICsgNik7CiAgfQogIGZvciBhd2FpdCAobGV0IGEgb2YgYikgewogICAgY29uc3QgYiA9IGF3YWl0IGYoYSk7CiAgICB5aWVsZCBiICsgYjsKICAgIGZyYW1lRWxlbWVudCgyKTsKICAgIGNvbnN0IHUgPSB5aWVsZCA2OwogIH0KICByZXR1cm4gZDsKfQoKYXN5bmMgZnVuY3Rpb24gY29udHJvbEZsb3dFeGFtcGxlKHgpIHsKICBjb25zdCBhID0gNTsKICBjb25zdCBiID0gYXdhaXQgdG90byg0NCk7CiAgd2hpbGUgKGIgPiAwKSB7CiAgICBpZiAoYikgewogICAgICByZXR1cm4gNTsKICAgIH0gZWxzZSBpZiAoYykgewogICAgICB0aHJvdyBuZXcgRXJyb3IoIk5vdCIpOwogICAgfSBlbHNlIHsKICAgICAgYiA9IDUyOwogICAgfQogIH0KICBkbyB7CiAgICBiID0gYiArIDE7CiAgICBjID0gZihrKTsKICAgIGlmIChrKSB7CiAgICAgIGJyZWFrOwogICAgfQogIH0gd2hpbGUgKGIgPCA0KTsKICBpZiAoaysrID09IDUpIHsKICAgIGNvdWNvdTIgPSAyNDsKICB9CiAgZm9yIChsZXQgdCA9IDA7IHQgPCA1OyB0KyspIHsKICAgIGlmIChsKSB7CiAgICAgIGYoaCk7CiAgICB9IGVsc2UgewogICAgICB3ZWJraXRDYW5jZWxBbmltYXRpb25GcmFtZSgzMik7CiAgICB9CiAgfQogIHJldHVybiBhICogMzsKfQoKY2xhc3MgRXhhbXBsZUNsYXNzIHsKICBteU1ldGhvZCh4KSB7CiAgICBjb25zb2xlLmxvZygibG9sIik7CiAgICByZXR1cm47CiAgfQogIGV4YW1wbGVNZXRob2QoeCkgewogICAgaWYgKG15Q29uZGl0aW9uKSByZXR1cm4gNTsKICAgIHJldHVybiA2OwogIH0KICBwcm9wZXJ0eSA9IDg7CiAgc3RhdGljIHN0YXRpY1Byb3AgPSAxOTsKICByZW5kZXIoKSB7CiAgICByZXR1cm4gKAogICAgICA8ZGl2PgogICAgICAgIDxzcGFuPlRvdG88L3NwYW4-CiAgICAgICAgPHNwYW4-VGl0aTwvc3Bhbj4KICAgICAgPC9kaXY-CiAgICApOwogIH0KfQoKZXhwb3J0IGRlZmF1bHQgdG90bzsK) 10 | 11 | ## CLI Usage 12 | 13 | Call the cli with a js file name, it prints out the Mermaid.js graph definition 14 | 15 | ```bash 16 | yarn global add code-to-graph 17 | code-to-graph example.js 18 | ``` 19 | 20 | To see the result visualy, paste it in 21 | 22 | https://mermaidjs.github.io/mermaid-live-editor 23 | 24 | Or you can use it on gitlab using the ```mermaid language in comments, descriptions or markdown files. 25 | 26 | ## Explanation 27 | 28 | Turns this: 29 | 30 | ```javascript 31 | x => { 32 | const a = f(x); 33 | if (x === 0) { 34 | let a = null; 35 | throw new Error("Nooes"); 36 | } else { 37 | const c = 8; 38 | return 4; 39 | } 40 | }; 41 | ``` 42 | 43 | Into this: 44 | 45 | ```mermaid 46 | graph TD 47 | statementfroml1c6tol1c19("const a = f(x);") 48 | statementfroml1c19tol1c90{"x === 0"} 49 | statementfroml1c29tol1c40("let a = null;") 50 | statementfroml1c41tol1c65>"throw new Error('Nooes');"] 51 | style statementfroml1c41tol1c65 fill:#FF9999 52 | statementfroml1c71tol1c81("const c = 8;") 53 | statementfroml1c81tol1c89>"return 4;"] 54 | style statementfroml1c81tol1c89 fill:#99FF99 55 | statementfroml1c19tol1c90 -- true --> statementfroml1c29tol1c40 56 | statementfroml1c19tol1c90 -- false --> statementfroml1c71tol1c81 57 | statementfroml1c29tol1c40 --> statementfroml1c41tol1c65 58 | statementfroml1c71tol1c81 --> statementfroml1c81tol1c89 59 | statementfroml1c6tol1c19 --> statementfroml1c19tol1c90 60 | ``` 61 | 62 | Whichs renders into this: 63 | 64 | ![Example mermaid diagram](./mermaid-diagram-example.svg) 65 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs"); 4 | const opn = require("opn"); 5 | const { transformJsStringToMermaidString } = require("."); 6 | 7 | const [, , ...args] = process.argv; 8 | 9 | // console.log(`Hello world ${args}`); 10 | 11 | function main(args) { 12 | const string = fs.readFileSync(args[0], { encoding: "utf8" }); 13 | const result = transformJsStringToMermaidString(string); 14 | console.log("\n\n"); 15 | console.log(result); 16 | console.log("\n\n"); 17 | try { 18 | const base64Result = Buffer.from(result).toString("base64"); 19 | opn( 20 | `https://mermaidjs.github.io/mermaid-live-editor/#/edit/${base64Result}`, 21 | { wait: false } 22 | ); 23 | } catch (e) { 24 | return; 25 | } 26 | } 27 | 28 | main(args); 29 | -------------------------------------------------------------------------------- /docs-source/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /docs-source/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /docs-source/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-to-graph", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://crubier.github.io/code-to-graph", 6 | "dependencies": { 7 | "@fortawesome/fontawesome-svg-core": "^1.2.12", 8 | "@fortawesome/free-solid-svg-icons": "^5.6.3", 9 | "@fortawesome/react-fontawesome": "^0.1.3", 10 | "attina": "^1.0.1", 11 | "brace": "^0.11.1", 12 | "code-to-graph": "1.0.15", 13 | "copy-to-clipboard": "^3.0.8", 14 | "file-saver": "^2.0.0", 15 | "js-base64": "^2.5.0", 16 | "lodash": "^4.17.11", 17 | "mermaid": "^8.0.0", 18 | "query-string": "^6.2.0", 19 | "react": "^16.7.0", 20 | "react-ace": "^6.3.2", 21 | "react-dom": "^16.7.0", 22 | "react-dropzone": "^8.0.3", 23 | "react-github-corner": "^2.3.0", 24 | "react-mermaid": "^0.1.3", 25 | "react-router": "^4.3.1", 26 | "react-router-dom": "^4.3.1", 27 | "react-scripts": "2.1.3" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "docs": "yarn build && rm -rf ../docs && mv ./build ../docs", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject" 35 | }, 36 | "eslintConfig": { 37 | "extends": "react-app" 38 | }, 39 | "browserslist": [ 40 | ">0.2%", 41 | "not dead", 42 | "not ie <= 11", 43 | "not op_mini all" 44 | ] 45 | } -------------------------------------------------------------------------------- /docs-source/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crubier/code-to-graph/b261d8233de9569ab2fc74f9d1ee520f7546b1dd/docs-source/public/favicon.ico -------------------------------------------------------------------------------- /docs-source/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 23 | Code to Graph 24 | 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs-source/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Code to Graph", 3 | "name": "Code to Graph", 4 | "icons": [{ 5 | "src": "favicon.ico", 6 | "sizes": "64x64 32x32 24x24 16x16", 7 | "type": "image/x-icon" 8 | }], 9 | "start_url": ".", 10 | "display": "standalone", 11 | "theme_color": "#000000", 12 | "background_color": "#ffffff" 13 | } -------------------------------------------------------------------------------- /docs-source/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs-source/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | // import logo from "./logo.svg"; 3 | import "./App.css"; 4 | 5 | import Editor from "./editor"; 6 | import GithubCorner from "react-github-corner"; 7 | import { BrowserRouter as Router, Route } from "react-router-dom"; 8 | 9 | class App extends Component { 10 | render() { 11 | return ( 12 | 13 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /docs-source/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /docs-source/src/attina.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { mermaidAPI } from "mermaid"; 3 | import FileSaver from "file-saver"; 4 | 5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 6 | import { faDownload } from "@fortawesome/free-solid-svg-icons"; 7 | 8 | // import debounce from "lodash/fp/debounce"; 9 | // mermaidAPI.initialize({ 10 | // startOnLoad: true, 11 | // logLevel: 1 12 | // }); 13 | 14 | function getDiagram(title, input, callback) { 15 | try { 16 | mermaidAPI.initialize({ 17 | startOnLoad: true 18 | }); 19 | 20 | mermaidAPI.parseError = function(err, hash) { 21 | // console.log("parseError"); 22 | // console.log(err.messsage); 23 | }; 24 | // console.log("Rendering"); 25 | // console.log(input); 26 | // mermaidAPI.render(title, "graph TB;Loading;", diagram => { 27 | // console.log("Rendered"); 28 | // setTimeout( 29 | // () => 30 | // mermaidAPI.render(title, input, diagram => { 31 | // console.log("Rendered"); 32 | // console.log(diagram); 33 | // callback(diagram); 34 | // }), 35 | // 200 36 | // ); 37 | // }); 38 | 39 | mermaidAPI.render(title, input, diagram => { 40 | // console.log("Rendered"); 41 | // console.log(diagram); 42 | callback({ diagram, input }); 43 | }); 44 | } catch (e) { 45 | console.log("Failed to generate diagram"); 46 | console.log(e); 47 | } 48 | } 49 | 50 | class Attina extends React.Component { 51 | constructor(props) { 52 | super(props); 53 | 54 | this.state = { 55 | diagram: { 56 | __html: "" 57 | } 58 | }; 59 | } 60 | 61 | componentDidMount() { 62 | getDiagram(this.props.title, this.props.diagram, ({ diagram, input }) => { 63 | this.setState({ diagram: { __html: diagram }, input }); 64 | }); 65 | } 66 | 67 | shouldComponentUpdate(nextProps, nextState) { 68 | if (nextProps.diagram !== this.props.diagram) { 69 | getDiagram(this.props.title, nextProps.diagram, ({ diagram, input }) => { 70 | this.setState({ diagram: { __html: diagram }, input }); 71 | }); 72 | 73 | return false; 74 | } 75 | if (nextState.diagram !== this.state.diagram) { 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | render() { 82 | return ( 83 | 84 | 118 |
128 | 129 | ); 130 | } 131 | } 132 | 133 | Attina.defaultProps = { 134 | title: "diagram", 135 | frameBorder: 0 136 | }; 137 | 138 | export default Attina; 139 | -------------------------------------------------------------------------------- /docs-source/src/editor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | // import logo from "./logo.svg"; 3 | import "./App.css"; 4 | import Attina from "./attina"; 5 | import debounce from "lodash/fp/debounce"; 6 | import { transformJsStringToMermaidString } from "code-to-graph"; 7 | import copy from "copy-to-clipboard"; 8 | 9 | import brace from "brace"; 10 | import AceEditor from "react-ace"; 11 | import Dropzone from "react-dropzone"; 12 | 13 | import "brace/mode/javascript"; 14 | import "brace/mode/text"; 15 | import "brace/snippets/javascript"; 16 | import "brace/theme/github"; 17 | import "brace/theme/monokai"; 18 | import "brace/ext/language_tools"; 19 | import "brace/ext/searchbox"; 20 | 21 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 22 | import { 23 | faSpinner, 24 | faPlay, 25 | faShareSquare, 26 | faUpload 27 | } from "@fortawesome/free-solid-svg-icons"; 28 | 29 | import { Base64 } from "js-base64"; 30 | 31 | import queryString from "query-string"; 32 | 33 | const getCodeFromLocation = ({ location }) => { 34 | try { 35 | // console.log("code"); 36 | const search = location.search; 37 | // console.log(search); 38 | const params = queryString.parse(search); 39 | // console.log(params); 40 | const codeBase64 = params.code; 41 | // console.log(codeBase64); 42 | const code = Base64.decode(codeBase64); 43 | // console.log(code); 44 | return { code: code }; 45 | } catch (e) { 46 | console.log("Using default code"); 47 | return { 48 | code: `const myFunction = (x) => { 49 | if(x>0){ 50 | return "ok" 51 | } else { 52 | throw "not ok" 53 | } 54 | }` 55 | }; 56 | } 57 | }; 58 | 59 | class App extends Component { 60 | constructor(props) { 61 | super(props); 62 | this.state = { 63 | source: `graph TB; Init;`, 64 | error: null, 65 | status: "init" 66 | }; 67 | 68 | this.handleChange = this.handleChange.bind(this); 69 | this.handleSubmitDirect = this.handleSubmitDirect.bind(this); 70 | this.handleSubmit = debounce(200, this.handleSubmitDirect).bind(this); 71 | } 72 | 73 | onDrop = (acceptedFiles, rejectedFiles) => { 74 | // console.log("drop", acceptedFiles, rejectedFiles); 75 | 76 | acceptedFiles.forEach(file => { 77 | const reader = new FileReader(); 78 | reader.onload = () => { 79 | const fileAsBinaryString = reader.result; 80 | this.handleChange(fileAsBinaryString); 81 | }; 82 | reader.onabort = () => console.log("file reading was aborted"); 83 | reader.onerror = () => console.log("file reading has failed"); 84 | 85 | reader.readAsBinaryString(file); 86 | }); 87 | }; 88 | 89 | handleChange(newValue) { 90 | const base64Result = Base64.encodeURI(newValue); 91 | this.props.history.push(`./?code=${base64Result}`); 92 | this.handleSubmit(); 93 | } 94 | 95 | handleSubmitDirect() { 96 | setTimeout(() => { 97 | const { code } = getCodeFromLocation({ location: this.props.location }); 98 | this.setState( 99 | { 100 | source: `graph TB; Loading;`, 101 | status: "loading" 102 | }, 103 | () => { 104 | setTimeout(() => { 105 | try { 106 | const source = transformJsStringToMermaidString(code); 107 | this.setState({ 108 | source: source, 109 | status: "loaded", 110 | error: null 111 | }); 112 | } catch (error) { 113 | this.setState({ 114 | status: "error", 115 | error: error 116 | }); 117 | } 118 | }, 0); 119 | } 120 | ); 121 | }, 0); 122 | } 123 | 124 | componentDidMount() { 125 | try { 126 | const { code } = getCodeFromLocation({ location: this.props.location }); 127 | this.handleChange(code); 128 | } catch (error) { 129 | this.setState({ 130 | status: "error", 131 | error: error 132 | }); 133 | } 134 | } 135 | 136 | render() { 137 | const { code } = getCodeFromLocation({ location: this.props.location }); 138 | return ( 139 | 140 | {({ getRootProps, getInputProps, isDragActive }) => { 141 | const inputProps = getInputProps(); 142 | // console.log(inputProps); 143 | return ( 144 |
156 | {isDragActive ? ( 157 |
173 | Drop file anywhere 174 |
175 | ) : null} 176 |
188 | 210 |
229 | 230 | 234 | {" "} 235 | Drop code files anywhere or 236 | 237 | {" "} 238 | 256 |
257 |
258 | 259 | 284 | {this.state.error === null || this.state.error === undefined ? ( 285 |
295 | 299 | 332 | 363 |
364 | ) : ( 365 |
375 | 407 |
408 | )} 409 |
410 | ); 411 | }} 412 |
413 | ); 414 | } 415 | } 416 | 417 | export default App; 418 | -------------------------------------------------------------------------------- /docs-source/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /docs-source/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /docs-source/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs-source/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /docs/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/code-to-graph/static/css/main.4fd1df1e.chunk.css", 3 | "main.js": "/code-to-graph/static/js/main.96947aa3.chunk.js", 4 | "main.js.map": "/code-to-graph/static/js/main.96947aa3.chunk.js.map", 5 | "static/js/1.b775380a.chunk.js": "/code-to-graph/static/js/1.b775380a.chunk.js", 6 | "static/js/1.b775380a.chunk.js.map": "/code-to-graph/static/js/1.b775380a.chunk.js.map", 7 | "runtime~main.js": "/code-to-graph/static/js/runtime~main.367d1e08.js", 8 | "runtime~main.js.map": "/code-to-graph/static/js/runtime~main.367d1e08.js.map", 9 | "static/css/main.4fd1df1e.chunk.css.map": "/code-to-graph/static/css/main.4fd1df1e.chunk.css.map", 10 | "index.html": "/code-to-graph/index.html", 11 | "precache-manifest.0f46b7a65bc3f0cdb2f6bf6e214ef341.js": "/code-to-graph/precache-manifest.0f46b7a65bc3f0cdb2f6bf6e214ef341.js", 12 | "service-worker.js": "/code-to-graph/service-worker.js" 13 | } -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crubier/code-to-graph/b261d8233de9569ab2fc74f9d1ee520f7546b1dd/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Code to Graph
-------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Code to Graph", 3 | "name": "Code to Graph", 4 | "icons": [{ 5 | "src": "favicon.ico", 6 | "sizes": "64x64 32x32 24x24 16x16", 7 | "type": "image/x-icon" 8 | }], 9 | "start_url": ".", 10 | "display": "standalone", 11 | "theme_color": "#000000", 12 | "background_color": "#ffffff" 13 | } -------------------------------------------------------------------------------- /docs/precache-manifest.0f46b7a65bc3f0cdb2f6bf6e214ef341.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "367d1e08377a7d6deb73", 4 | "url": "/code-to-graph/static/js/runtime~main.367d1e08.js" 5 | }, 6 | { 7 | "revision": "96947aa3bc5e99ad6822", 8 | "url": "/code-to-graph/static/js/main.96947aa3.chunk.js" 9 | }, 10 | { 11 | "revision": "b775380acf87012bed38", 12 | "url": "/code-to-graph/static/js/1.b775380a.chunk.js" 13 | }, 14 | { 15 | "revision": "96947aa3bc5e99ad6822", 16 | "url": "/code-to-graph/static/css/main.4fd1df1e.chunk.css" 17 | }, 18 | { 19 | "revision": "027774cdcf6548e2b7cd9cbfae5f7514", 20 | "url": "/code-to-graph/index.html" 21 | } 22 | ]; -------------------------------------------------------------------------------- /docs/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/code-to-graph/precache-manifest.0f46b7a65bc3f0cdb2f6bf6e214ef341.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/code-to-graph/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /docs/static/css/main.4fd1df1e.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{text-align:center}.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite;height:40vmin}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@-webkit-keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}} 2 | /*# sourceMappingURL=main.4fd1df1e.chunk.css.map */ -------------------------------------------------------------------------------- /docs/static/css/main.4fd1df1e.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["/Users/vincent/Code/code-to-graph/docs-source/src/index.css","/Users/vincent/Code/code-to-graph/docs-source/src/App.css"],"names":[],"mappings":"AAAA,KACE,SAAU,AACV,UAAW,AACX,oIAEa,AACb,mCAAoC,AACpC,iCAAmC,CACpC,AAED,KACE,uEACY,CACb,ACbD,KACE,iBAAmB,CACpB,AAED,UACE,oDAA6C,AAA7C,4CAA6C,AAC7C,aAAe,CAChB,AAED,YACE,yBAA0B,AAC1B,iBAAkB,AAClB,aAAc,AACd,sBAAuB,AACvB,mBAAoB,AACpB,uBAAwB,AACxB,6BAA8B,AAC9B,UAAa,CACd,AAED,UACE,aAAe,CAChB,AAED,iCACE,GACE,+BAAwB,AAAxB,sBAAwB,CACzB,AACD,GACE,gCAA0B,AAA1B,uBAA0B,CAC3B,CACF,AAPD,yBACE,GACE,+BAAwB,AAAxB,sBAAwB,CACzB,AACD,GACE,gCAA0B,AAA1B,uBAA0B,CAC3B,CACF","file":"main.4fd1df1e.chunk.css","sourcesContent":["body {\n margin: 0;\n padding: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\",\n \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\",\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, \"Courier New\",\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.App-logo {\n animation: App-logo-spin infinite 20s linear;\n height: 40vmin;\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n"]} -------------------------------------------------------------------------------- /docs/static/js/main.96947aa3.chunk.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{190:function(e,t,n){e.exports=n(455)},195:function(e,t,n){},455:function(e,t,n){"use strict";n.r(t);var a=n(1),o=n.n(a),r=n(179),i=n.n(r),l=(n(195),n(24)),s=n(25),c=n(27),d=n(26),u=n(28),h=(n(93),n(14)),m=n(189),f=n(54),p=n(180),g=n.n(p),b=n(21),x=n(22);function y(e,t,n){try{f.mermaidAPI.initialize({startOnLoad:!0}),f.mermaidAPI.parseError=function(e,t){},f.mermaidAPI.render(e,t,function(e){n({diagram:e,input:t})})}catch(a){console.log("Failed to generate diagram"),console.log(a)}}var v=function(e){function t(e){var n;return Object(l.a)(this,t),(n=Object(c.a)(this,Object(d.a)(t).call(this,e))).state={diagram:{__html:""}},n}return Object(u.a)(t,e),Object(s.a)(t,[{key:"componentDidMount",value:function(){var e=this;y(this.props.title,this.props.diagram,function(t){var n=t.diagram,a=t.input;e.setState({diagram:{__html:n},input:a})})}},{key:"shouldComponentUpdate",value:function(e,t){var n=this;return e.diagram!==this.props.diagram?(y(this.props.title,e.diagram,function(e){var t=e.diagram,a=e.input;n.setState({diagram:{__html:t},input:a})}),!1):t.diagram!==this.state.diagram}},{key:"render",value:function(){return o.a.createElement(o.a.Fragment,null,o.a.createElement("button",{onClick:function(){var e=new Blob([document.getElementsByClassName("mermaid-code-to-graph")[0].innerHTML],{type:"image/svg+xml;charset=utf-8"});g.a.saveAs(e,"graph.svg")},style:{color:"white",borderRadius:"0",border:"none",outline:"none",backgroundColor:"#FF44FF",flexBasis:"30px",flexShrink:0,flexGrow:1,width:"100%",minHeight:"30px",zIndex:1}},o.a.createElement(b.a,{icon:x.a})," "," "," ",(this.state.status,"Download SVG File")),o.a.createElement("div",{dangerouslySetInnerHTML:this.state.diagram,className:"mermaid-code-to-graph",style:Object(m.a)({display:"flex",alignItems:"center",justifyContent:"center"},this.props.style)}))}}]),t}(o.a.Component);v.defaultProps={title:"diagram",frameBorder:0};var w=v,S=n(181),k=n.n(S),E=n(182),j=n(183),O=n.n(j),C=(n(89),n(55)),F=n.n(C),B=n(188),D=(n(443),n(445),n(446),n(447),n(448),n(449),n(450),n(91)),I=n(185),G=n.n(I),z=function(e){var t=e.location;try{var n=t.search,a=G.a.parse(n).code;return{code:D.Base64.decode(a)}}catch(o){return console.log("Using default code"),{code:'const myFunction = (x) => {\n if(x>0){\n return "ok"\n } else {\n throw "not ok"\n }\n}'}}},A=function(e){function t(e){var n;return Object(l.a)(this,t),(n=Object(c.a)(this,Object(d.a)(t).call(this,e))).onDrop=function(e,t){e.forEach(function(e){var t=new FileReader;t.onload=function(){var e=t.result;n.handleChange(e)},t.onabort=function(){return console.log("file reading was aborted")},t.onerror=function(){return console.log("file reading has failed")},t.readAsBinaryString(e)})},n.state={source:"graph TB; Init;",error:null,status:"init"},n.handleChange=n.handleChange.bind(Object(h.a)(Object(h.a)(n))),n.handleSubmitDirect=n.handleSubmitDirect.bind(Object(h.a)(Object(h.a)(n))),n.handleSubmit=k()(200,n.handleSubmitDirect).bind(Object(h.a)(Object(h.a)(n))),n}return Object(u.a)(t,e),Object(s.a)(t,[{key:"handleChange",value:function(e){var t=D.Base64.encodeURI(e);this.props.history.push("./?code=".concat(t)),this.handleSubmit()}},{key:"handleSubmitDirect",value:function(){var e=this;setTimeout(function(){var t=z({location:e.props.location}).code;e.setState({source:"graph TB; Loading;",status:"loading"},function(){setTimeout(function(){try{var n=Object(E.transformJsStringToMermaidString)(t);e.setState({source:n,status:"loaded",error:null})}catch(a){e.setState({status:"error",error:a})}},0)})},0)}},{key:"componentDidMount",value:function(){try{var e=z({location:this.props.location}).code;this.handleChange(e)}catch(t){this.setState({status:"error",error:t})}}},{key:"render",value:function(){var e=this,t=z({location:this.props.location}).code;return o.a.createElement(B.a,{onDrop:this.onDrop},function(n){var a=n.getRootProps,r=n.getInputProps,i=n.isDragActive,l=r();return o.a.createElement("div",Object.assign({},a(),{onClick:void 0,style:{display:"flex",flexDirection:"row",height:"100vh",alignItems:"stretch",flexWrap:"wrap"}}),i?o.a.createElement("div",{style:{position:"absolute",color:"white",fontSize:"2em",display:"flex",alignItems:"center",justifyContent:"center",left:0,right:0,top:0,bottom:0,backgroundColor:"rgba(0,0,0,0.8)",zIndex:99999}},"Drop file anywhere"):null,o.a.createElement("div",{style:{flexBasis:"50%",flexShrink:1,flexGrow:1,height:"100%",display:"flex",flexDirection:"column",alignItems:"stretch",justifyContent:"stretch"}},o.a.createElement(F.a,{mode:"javascript",theme:"monokai",onChange:e.handleChange,name:"code-ace-editor",editorProps:{$blockScrolling:!0},value:t,style:{flexBasis:"100vh",flexShrink:1,flexGrow:1,width:"100%"},setOptions:{enableBasicAutocompletion:!0,enableLiveAutocompletion:!0,enableSnippets:!0,showLineNumbers:!0,tabSize:2}}),o.a.createElement("div",{style:{color:"white",borderRadius:"0",border:"none",outline:"none",backgroundColor:"#FF44FF",flexBasis:"30px",flexShrink:0,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"center",flexGrow:1,width:"100%",minHeight:"30px"}},o.a.createElement("span",{style:{fontSize:"11px",marginRight:"1em"}},o.a.createElement(b.a,{icon:x.e,style:{flexShrink:1,flexGrow:1}})," ","Drop code files anywhere or")," ",o.a.createElement("input",Object.assign({},l,{style:{color:"white",borderRadius:"0",border:"none",outline:"none",backgroundColor:"#FF44FF",flexBasis:"fit-content",flexShrink:1,flexGrow:0,display:"flex",flexDirection:"row",alignItems:"center",justifyContent:"center"}})))),o.a.createElement("button",{onClick:e.handleSubmitDirect,style:{position:"relative",fontWeight:600,outline:"none",color:"white",fontSize:"1.2em",borderRadius:"100%",border:"none",backgroundColor:"#FF44FF",width:60,height:60,marginLeft:-30,marginRight:-30,alignSelf:"center",zIndex:9999}},"loading"!==e.state.status?o.a.createElement(b.a,{icon:x.b}):o.a.createElement(b.a,{icon:x.d,spin:!0})),null===e.state.error||void 0===e.state.error?o.a.createElement("div",{style:{flexBasis:"50%",flexShrink:1,flexGrow:0,height:"100%",display:"flex",flexDirection:"column"}},o.a.createElement(w,{diagram:e.state.source,style:{flexBasis:"80%",flexShrink:1,flexGrow:1}}),o.a.createElement("button",{onClick:function(){O()("```mermaid\n"+e.state.source+"\n```",{debug:!0,message:"Press #{key} to copy"}),e.setState({status:"copied"})},style:{color:"white",borderRadius:"0",border:"none",outline:"none",backgroundColor:"#FF44FF",flexBasis:"30px",flexShrink:0,flexGrow:1,width:"100%",minHeight:"30px"}},"copied"!==e.state.status?o.a.createElement(o.a.Fragment,null,o.a.createElement(b.a,{icon:x.c})," ","Copy graph code to clipboard"):"Copied!"),o.a.createElement(F.a,{mode:"text",theme:"github",wrapEnabled:!1,readOnly:!0,name:"result-ace-editor",editorProps:{$blockScrolling:!0},value:"```mermaid\n"+e.state.source+"\n```",style:{flexBasis:"20%",flexShrink:0,flexGrow:0,width:"100%",height:"20%"},setOptions:{enableBasicAutocompletion:!1,enableLiveAutocompletion:!1,enableSnippets:!1,showLineNumbers:!1,tabSize:2}})):o.a.createElement("div",{style:{flexBasis:"50%",flexShrink:0,flexGrow:0,height:"100%",display:"flex",flexDirection:"column"}},o.a.createElement(F.a,{mode:"text",theme:"github",readOnly:!0,wrapEnabled:!0,name:"error-ace-editor",editorProps:{$blockScrolling:!0},value:e.state.error.message,style:{flexBasis:"100%",flexShrink:0,flexGrow:0,width:"100%",height:"100%",color:"red"},setOptions:{enableBasicAutocompletion:!1,enableLiveAutocompletion:!1,enableSnippets:!1,showLineNumbers:!1,tabSize:2}})))})}}]),t}(a.Component),L=n(186),P=n.n(L),R=n(457),T=n(458),_=function(e){function t(){return Object(l.a)(this,t),Object(c.a)(this,Object(d.a)(t).apply(this,arguments))}return Object(u.a)(t,e),Object(s.a)(t,[{key:"render",value:function(){return o.a.createElement(o.a.Fragment,null,o.a.createElement(P.a,{href:"https://github.com/crubier/code-to-graph",style:{zIndex:20},svgStyle:{zIndex:20}}),o.a.createElement(R.a,null,o.a.createElement(T.a,{component:A})))}}]),t}(a.Component);Boolean("localhost"===window.location.hostname||"[::1]"===window.location.hostname||window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/));i.a.render(o.a.createElement(_,null),document.getElementById("root")),"serviceWorker"in navigator&&navigator.serviceWorker.ready.then(function(e){e.unregister()})},93:function(e,t,n){}},[[190,2,1]]]); 2 | //# sourceMappingURL=main.96947aa3.chunk.js.map -------------------------------------------------------------------------------- /docs/static/js/main.96947aa3.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["attina.js","editor.js","App.js","serviceWorker.js","index.js"],"names":["getDiagram","title","input","callback","mermaidAPI","initialize","startOnLoad","parseError","err","hash","render","diagram","e","console","log","Attina","props","_this","Object","classCallCheck","this","possibleConstructorReturn","getPrototypeOf","call","state","__html","_this2","_ref","setState","nextProps","nextState","_this3","_ref2","react_default","a","createElement","Fragment","onClick","blob","Blob","document","getElementsByClassName","innerHTML","type","FileSaver","saveAs","style","color","borderRadius","border","outline","backgroundColor","flexBasis","flexShrink","flexGrow","width","minHeight","zIndex","index_es","icon","faDownload","status","dangerouslySetInnerHTML","className","objectSpread","display","alignItems","justifyContent","React","Component","defaultProps","frameBorder","getCodeFromLocation","location","search","codeBase64","queryString","parse","code","Base64","decode","App","onDrop","acceptedFiles","rejectedFiles","forEach","file","reader","FileReader","onload","fileAsBinaryString","result","handleChange","onabort","onerror","readAsBinaryString","source","error","bind","assertThisInitialized","handleSubmitDirect","handleSubmit","debounce","newValue","base64Result","encodeURI","history","push","concat","setTimeout","transformJsStringToMermaidString","es","getRootProps","getInputProps","isDragActive","inputProps","assign","undefined","flexDirection","height","flexWrap","position","fontSize","left","right","top","bottom","lib_default","mode","theme","onChange","name","editorProps","$blockScrolling","value","setOptions","enableBasicAutocompletion","enableLiveAutocompletion","enableSnippets","showLineNumbers","tabSize","marginRight","faUpload","fontWeight","marginLeft","alignSelf","faPlay","faSpinner","spin","attina","copy","debug","message","faShareSquare","wrapEnabled","readOnly","GithubCorner_default","href","svgStyle","BrowserRouter","Route","component","Editor","Boolean","window","hostname","match","ReactDOM","src_App_0","getElementById","navigator","serviceWorker","ready","then","registration","unregister"],"mappings":"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","file":"static/js/main.96947aa3.chunk.js","sourcesContent":["import React from \"react\";\nimport { mermaidAPI } from \"mermaid\";\nimport FileSaver from \"file-saver\";\n\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport { faDownload } from \"@fortawesome/free-solid-svg-icons\";\n\n// import debounce from \"lodash/fp/debounce\";\n// mermaidAPI.initialize({\n// startOnLoad: true,\n// logLevel: 1\n// });\n\nfunction getDiagram(title, input, callback) {\n try {\n mermaidAPI.initialize({\n startOnLoad: true\n });\n\n mermaidAPI.parseError = function(err, hash) {\n // console.log(\"parseError\");\n // console.log(err.messsage);\n };\n // console.log(\"Rendering\");\n // console.log(input);\n // mermaidAPI.render(title, \"graph TB;Loading;\", diagram => {\n // console.log(\"Rendered\");\n // setTimeout(\n // () =>\n // mermaidAPI.render(title, input, diagram => {\n // console.log(\"Rendered\");\n // console.log(diagram);\n // callback(diagram);\n // }),\n // 200\n // );\n // });\n\n mermaidAPI.render(title, input, diagram => {\n // console.log(\"Rendered\");\n // console.log(diagram);\n callback({ diagram, input });\n });\n } catch (e) {\n console.log(\"Failed to generate diagram\");\n console.log(e);\n }\n}\n\nclass Attina extends React.Component {\n constructor(props) {\n super(props);\n\n this.state = {\n diagram: {\n __html: \"\"\n }\n };\n }\n\n componentDidMount() {\n getDiagram(this.props.title, this.props.diagram, ({ diagram, input }) => {\n this.setState({ diagram: { __html: diagram }, input });\n });\n }\n\n shouldComponentUpdate(nextProps, nextState) {\n if (nextProps.diagram !== this.props.diagram) {\n getDiagram(this.props.title, nextProps.diagram, ({ diagram, input }) => {\n this.setState({ diagram: { __html: diagram }, input });\n });\n\n return false;\n }\n if (nextState.diagram !== this.state.diagram) {\n return true;\n }\n return false;\n }\n\n render() {\n return (\n \n {\n var blob = new Blob(\n [\n document.getElementsByClassName(\"mermaid-code-to-graph\")[0]\n .innerHTML\n ],\n {\n type: \"image/svg+xml;charset=utf-8\"\n }\n );\n FileSaver.saveAs(blob, \"graph.svg\");\n }}\n style={{\n color: \"white\",\n // fontSize: \"1.2em\",\n borderRadius: \"0\",\n border: \"none\",\n outline: \"none\",\n backgroundColor: \"#FF44FF\",\n flexBasis: \"30px\",\n flexShrink: 0,\n flexGrow: 1,\n width: \"100%\",\n minHeight: \"30px\",\n zIndex: 1\n }}\n >\n \n {\" \"} {\" \"}\n {this.state.status !== \"loading\"\n ? \"Download SVG File\"\n : \"Download SVG File\"}\n \n \n \n );\n }\n}\n\nAttina.defaultProps = {\n title: \"diagram\",\n frameBorder: 0\n};\n\nexport default Attina;\n","import React, { Component } from \"react\";\n// import logo from \"./logo.svg\";\nimport \"./App.css\";\nimport Attina from \"./attina\";\nimport debounce from \"lodash/fp/debounce\";\nimport { transformJsStringToMermaidString } from \"code-to-graph\";\nimport copy from \"copy-to-clipboard\";\n\nimport brace from \"brace\";\nimport AceEditor from \"react-ace\";\nimport Dropzone from \"react-dropzone\";\n\nimport \"brace/mode/javascript\";\nimport \"brace/mode/text\";\nimport \"brace/snippets/javascript\";\nimport \"brace/theme/github\";\nimport \"brace/theme/monokai\";\nimport \"brace/ext/language_tools\";\nimport \"brace/ext/searchbox\";\n\nimport { FontAwesomeIcon } from \"@fortawesome/react-fontawesome\";\nimport {\n faSpinner,\n faPlay,\n faShareSquare,\n faUpload\n} from \"@fortawesome/free-solid-svg-icons\";\n\nimport { Base64 } from \"js-base64\";\n\nimport queryString from \"query-string\";\n\nconst getCodeFromLocation = ({ location }) => {\n try {\n // console.log(\"code\");\n const search = location.search;\n // console.log(search);\n const params = queryString.parse(search);\n // console.log(params);\n const codeBase64 = params.code;\n // console.log(codeBase64);\n const code = Base64.decode(codeBase64);\n // console.log(code);\n return { code: code };\n } catch (e) {\n console.log(\"Using default code\");\n return {\n code: `const myFunction = (x) => {\n if(x>0){\n return \"ok\"\n } else {\n throw \"not ok\"\n }\n}`\n };\n }\n};\n\nclass App extends Component {\n constructor(props) {\n super(props);\n this.state = {\n source: `graph TB; Init;`,\n error: null,\n status: \"init\"\n };\n\n this.handleChange = this.handleChange.bind(this);\n this.handleSubmitDirect = this.handleSubmitDirect.bind(this);\n this.handleSubmit = debounce(200, this.handleSubmitDirect).bind(this);\n }\n\n onDrop = (acceptedFiles, rejectedFiles) => {\n // console.log(\"drop\", acceptedFiles, rejectedFiles);\n\n acceptedFiles.forEach(file => {\n const reader = new FileReader();\n reader.onload = () => {\n const fileAsBinaryString = reader.result;\n this.handleChange(fileAsBinaryString);\n };\n reader.onabort = () => console.log(\"file reading was aborted\");\n reader.onerror = () => console.log(\"file reading has failed\");\n\n reader.readAsBinaryString(file);\n });\n };\n\n handleChange(newValue) {\n const base64Result = Base64.encodeURI(newValue);\n this.props.history.push(`./?code=${base64Result}`);\n this.handleSubmit();\n }\n\n handleSubmitDirect() {\n setTimeout(() => {\n const { code } = getCodeFromLocation({ location: this.props.location });\n this.setState(\n {\n source: `graph TB; Loading;`,\n status: \"loading\"\n },\n () => {\n setTimeout(() => {\n try {\n const source = transformJsStringToMermaidString(code);\n this.setState({\n source: source,\n status: \"loaded\",\n error: null\n });\n } catch (error) {\n this.setState({\n status: \"error\",\n error: error\n });\n }\n }, 0);\n }\n );\n }, 0);\n }\n\n componentDidMount() {\n try {\n const { code } = getCodeFromLocation({ location: this.props.location });\n this.handleChange(code);\n } catch (error) {\n this.setState({\n status: \"error\",\n error: error\n });\n }\n }\n\n render() {\n const { code } = getCodeFromLocation({ location: this.props.location });\n return (\n \n {({ getRootProps, getInputProps, isDragActive }) => {\n const inputProps = getInputProps();\n // console.log(inputProps);\n return (\n \n {isDragActive ? (\n \n Drop file anywhere\n
\n ) : null}\n \n \n \n \n \n {\" \"}\n Drop code files anywhere or\n \n {\" \"}\n \n \n \n\n \n {this.state.status !== \"loading\" ? (\n \n ) : (\n \n )}\n \n {this.state.error === null || this.state.error === undefined ? (\n \n \n {\n // Copy with options\n copy(\"```mermaid\\n\" + this.state.source + \"\\n```\", {\n debug: true,\n message: \"Press #{key} to copy\"\n });\n this.setState({ status: \"copied\" });\n }}\n style={{\n color: \"white\",\n // fontSize: \"1.2em\",\n borderRadius: \"0\",\n border: \"none\",\n outline: \"none\",\n backgroundColor: \"#FF44FF\",\n flexBasis: \"30px\",\n flexShrink: 0,\n flexGrow: 1,\n width: \"100%\",\n minHeight: \"30px\"\n }}\n >\n {this.state.status !== \"copied\" ? (\n \n \n {\" \"}\n Copy graph code to clipboard\n \n ) : (\n \"Copied!\"\n )}\n \n \n \n ) : (\n \n \n \n )}\n \n );\n }}\n \n );\n }\n}\n\nexport default App;\n","import React, { Component } from \"react\";\n// import logo from \"./logo.svg\";\nimport \"./App.css\";\n\nimport Editor from \"./editor\";\nimport GithubCorner from \"react-github-corner\";\nimport { BrowserRouter as Router, Route } from \"react-router-dom\";\n\nclass App extends Component {\n render() {\n return (\n \n \n \n \n \n \n );\n }\n}\n\nexport default App;\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read http://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\nexport function register(config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit http://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl, config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl, config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(, document.getElementById('root'));\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: http://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/static/js/runtime~main.367d1e08.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c { 38 | try { 39 | const image = await getImageData(inputOptions); 40 | const closestStructures = await findClosestStructures( 41 | image, 42 | inputOptions.userId, 43 | 30 44 | ); 45 | if (closestStructures.status != 200) { 46 | return closestStructures; 47 | } 48 | 49 | image.closestStructure = closestStructures.body[0]; 50 | console.log(`INFO: closest structure Id: ${image.closestStructure.id}`); 51 | console.log(`INFO: closest structure Name: ${image.closestStructure.name}`); 52 | console.log( 53 | `INFO: closest structure Altitude: ${ 54 | image.closestStructure.location.altitude 55 | }` 56 | ); 57 | console.log( 58 | `INFO: closest structure Latitude: ${ 59 | image.closestStructure.location.latitude 60 | }` 61 | ); 62 | console.log( 63 | `INFO: closest structure Longitude: ${ 64 | image.closestStructure.location.longitude 65 | }` 66 | ); 67 | console.log( 68 | `INFO: closest structure params: ${JSON.stringify( 69 | image.closestStructure.parameters, 70 | null, 71 | 2 72 | )} ` 73 | ); 74 | 75 | if (isNil(image.mission.altitudeOffset) || !inputOptions.scene3D) { 76 | console.log(`INFO: using scene 2D`); 77 | image.location.altitude = image.closestStructure.altitude; 78 | image.location.pitch = 0; 79 | map(structure => { 80 | structure.altitude = image.closestStructure.altitude; 81 | }, closestStructures.body); 82 | console.log( 83 | `INFO: updating image and all structures altitude to ${ 84 | image.closestStructure.altitude 85 | }` 86 | ); 87 | } 88 | const geoOrigin = [ 89 | image.closestStructure.latitude, 90 | image.closestStructure.longitude, 91 | image.closestStructure.altitude 92 | ]; 93 | const scene = await createScene({ 94 | images: [image], 95 | structures: closestStructures.body, 96 | geoOrigin: geoOrigin, 97 | buffer: inputOptions.structureBuffer, 98 | cameraOptions: { 99 | near: 0.5, 100 | far: 30 101 | } 102 | }); 103 | if (inputOptions.saveScene) { 104 | await writeJsonFile(`${image.id}_locatorScene.json`, scene.toJSON()); 105 | } 106 | let intersection = find(structure => { 107 | return doesIntersect(scene, image.id, structure.id); 108 | }, closestStructures.body); 109 | if (isNil(intersection)) { 110 | console.log("INFO: FOUND NO INTERSECTION FOR THE IMAGE !!"); 111 | console.log("INFO: USING CLOSEST STRUCTURE TO THE IMAGE !!"); 112 | intersection = image.closestStructure; 113 | } else { 114 | console.log( 115 | `INFO: Found intersection with structure name ${ 116 | intersection.name 117 | } and structure id ${intersection.id}` 118 | ); 119 | } 120 | if (inputOptions.updateDataBase) { 121 | let resultPosition = {}; 122 | if (isNil(image.mission)) { 123 | image.mission = intersection.mission; 124 | } 125 | const resultValidity = await updateImageValidityWrapper({ 126 | imageId: image.id, 127 | missionExecutionId: { 128 | connect: { id: image.mission.id } 129 | }, 130 | structureIds: { 131 | connect: [{ id: intersection.id }] 132 | } 133 | }); 134 | console.log( 135 | `INFO: updated ${image.id} with missionExecution ${ 136 | image.mission.id 137 | } and structure ${intersection.name} with id ${intersection.id}` 138 | ); 139 | if (intersection.model.type == "HighVoltagePowerLinePylon") { 140 | const position = { 141 | height: image.location.altitude - intersection.altitude, 142 | angleToPylonOrientation: Math.PI - image.location.yaw 143 | }; 144 | resultPosition = await updateImagePositionWrapper( 145 | image.location.id, 146 | position 147 | ); 148 | { 149 | console.log( 150 | `INFO: Image Position % HTB ${JSON.stringify(position, null, 2)}` 151 | ); 152 | } 153 | } 154 | return { ...resultValidity, ...resultPosition }; 155 | } else { 156 | return { status: 200, body: `intersection is ${intersection.id}` }; 157 | } 158 | } catch (e) { 159 | console.log(e); 160 | } 161 | }; 162 | 163 | export const handler = async (event, context, callback) => { 164 | let imageId = event.imageId; 165 | let userId = event.userId; 166 | if (isNil(imageId)) { 167 | imageId = event.body.imageId; 168 | userId = event.body.userId; 169 | } 170 | 171 | console.log(`IMAGEID :: ${imageId}`); 172 | console.log(`USERID :: ${userId}`); 173 | const result = await main({ 174 | imageId: imageId, 175 | userId: userId, 176 | scene3D: true, 177 | structureBuffer: 5, 178 | saveScene: false, 179 | updateDataBase: true 180 | }); 181 | // const orgas = await getOrganizationByUserWrapper(userId); 182 | // const funcName = process.env.IMAGE_INSPECTOR; 183 | // console.log("image inspector name", funcName); 184 | // await Promise.map( 185 | // orgas, 186 | // async orga => { 187 | // try { 188 | // console.log("running inspector for", orga.name); 189 | // const res = await invokeLambda(funcName)({ 190 | // imageId: imageId, 191 | // organizationId: orga.id 192 | // }); 193 | // console.log(res); 194 | // } catch (e) { 195 | // console.log(e); 196 | // } 197 | // }, 198 | // { concurrency: 20 } 199 | // ); 200 | 201 | callback(null, result); 202 | }; 203 | 204 | const run = async () => { 205 | const userId = "cjod5xngh0s060170t1bss74x"; 206 | const filter = { 207 | structures_some: { id: "cjik2b2pnkb5z01092b20912p" }, 208 | mission: { mission: { id: "cjpjtczrn43on0c3229h75thl" } }, 209 | file: { id_not: null } 210 | }; 211 | 212 | const imageList = await getListImagesByFilter({ 213 | filter: filter 214 | }); 215 | console.log(`got ${imageList.images.length} images`); 216 | const funcName = `image-pipeline-${process.env.STAGE}-locatorHandler`; 217 | console.log("Using ", funcName); 218 | await Promise.map( 219 | imageList.images, 220 | async image => { 221 | try { 222 | const res = await handler( 223 | { 224 | imageId: image.id, 225 | userId: userId 226 | }, 227 | null, 228 | function(aes, res) { 229 | console.log(res); 230 | } 231 | ); 232 | // const res = await invokeLambda(funcName)({ 233 | // imageId: image.id, 234 | // userId: userId 235 | // }); 236 | // if (res.StatusCode == 200) { 237 | // const result = JSON.parse(res.Payload); 238 | // console.log( 239 | // "SUCCESS :: image:", 240 | // image.id, 241 | // "vaildity", 242 | // result[0].valid 243 | // ); 244 | // } else { 245 | // console.log("ERROR :: image:", image.id); 246 | // } 247 | } catch (e) { 248 | console.log(e); 249 | } 250 | }, 251 | { concurrency: 3 } 252 | ); 253 | }; 254 | 255 | if (require.main === module) { 256 | // run(); 257 | handler( 258 | { 259 | imageId: "cjpo38p72m25j0c32chygfxod", 260 | userId: "cjod5xngh0s060170t1bss74x" 261 | }, 262 | null, 263 | function(aes, res) { 264 | console.log(res); 265 | } 266 | ); 267 | } 268 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | import { toto } from "tutu"; 2 | 3 | function tryCatchExample(args) { 4 | function f() { 5 | const d = async () => { 6 | await 5; 7 | return toto; 8 | }; 9 | const ff = 45; 10 | try { 11 | let a = 5; 12 | a = a + 1; 13 | if (a > 3) { 14 | throw new Error("not normal"); 15 | } 16 | } catch (e) { 17 | console.log("in the catch"); 18 | if (v) return 5; 19 | if (ftw) throw new Error("Real bad"); 20 | } finally { 21 | console.log("in the finally"); 22 | if (u) return 8; 23 | } 24 | return 1; 25 | } 26 | } 27 | 28 | export async function* generatorExample(args) { 29 | yield 1; 30 | yield 5; 31 | if (myCond) { 32 | const c = yield 21; 33 | const d = await coco(c); 34 | } else { 35 | yield 25; 36 | let u = yield f(45 + 6); 37 | } 38 | for await (let a of b) { 39 | const b = await f(a); 40 | yield b + b; 41 | frameElement(2); 42 | const u = yield 6; 43 | } 44 | return d; 45 | } 46 | 47 | async function controlFlowExample(x) { 48 | const a = 5; 49 | const b = await toto(44); 50 | while (b > 0) { 51 | if (b) { 52 | return 5; 53 | } else if (c) { 54 | throw new Error("Not"); 55 | } else { 56 | b = 52; 57 | } 58 | } 59 | do { 60 | b = b + 1; 61 | c = f(k); 62 | if (k) { 63 | break; 64 | } 65 | } while (b < 4); 66 | if (k++ == 5) { 67 | coucou2 = 24; 68 | } 69 | for (let t = 0; t < 5; t++) { 70 | if (l) { 71 | f(h); 72 | } else { 73 | webkitCancelAnimationFrame(32); 74 | } 75 | } 76 | return a * 3; 77 | } 78 | 79 | class ExampleClass { 80 | myMethod(x) { 81 | console.log("lol"); 82 | return; 83 | } 84 | exampleMethod(x) { 85 | if (myCondition) return 5; 86 | return 6; 87 | } 88 | property = 8; 89 | static staticProp = 19; 90 | render() { 91 | return ( 92 |
93 | Toto 94 | Titi 95 |
96 | ); 97 | } 98 | } 99 | 100 | export default toto; 101 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const babelParser = require("@babel/parser"); 2 | 3 | const fp = require("lodash/fp"); 4 | 5 | const { transformGeneralAstToGraph } = require("./transform.js"); 6 | 7 | function transformJsStringToJsAst(string) { 8 | return babelParser.parse(string, { 9 | sourceType: "module", 10 | plugins: ["classProperties", "asyncGenerators", "jsx", "typescript"] 11 | }); 12 | } 13 | 14 | function transformJsAstToGraph(ast) { 15 | // if ( 16 | // ast.type === "FunctionExpression" || 17 | // ast.type === "ArrowFunctionExpression" 18 | // ) { 19 | return transformGeneralAstToGraph(ast); 20 | // } else if (ast){}else{ 21 | // console.log(JSON.stringify(ast, null, 2)); 22 | // throw new Error(`Code must be a FunctionExpression, but is a ${ast.type}`); 23 | // } 24 | } 25 | 26 | function transformNodeToMermaidString({ id, name, shape = "square", style }) { 27 | const styleText = fp.isObject(style) 28 | ? `\n style ${id} ${fp.join( 29 | ",", 30 | fp.map( 31 | ([key, value]) => `${fp.kebabCase(key)}:${value}`, 32 | fp.toPairs(style) 33 | ) 34 | )}` 35 | : ""; 36 | const text = fp.isString(name) ? name : id; 37 | switch (shape) { 38 | case "round": 39 | return ` ${id}(${text})${styleText}`; 40 | case "circle": 41 | return ` ${id}((${text}))${styleText}`; 42 | case "asymetric": 43 | return ` ${id}>${text}]${styleText}`; 44 | case "rhombus": 45 | return ` ${id}{${text}}${styleText}`; 46 | case "square": 47 | default: 48 | return ` ${id}[${text}]${styleText}`; 49 | } 50 | } 51 | 52 | function transformEdgeToMermaidString({ 53 | from, 54 | to, 55 | name, 56 | type = "solid", 57 | arrow = true 58 | }) { 59 | const text = fp.isString(name) ? name : ""; 60 | const arrowText = arrow ? ">" : ""; 61 | if (fp.isEmpty(text)) { 62 | switch (type) { 63 | case "dotted": 64 | return ` ${from} -.-${arrowText} ${to}`; 65 | case "thick": 66 | return ` ${from} ==${arrowText} ${to}`; 67 | case "solid": 68 | default: 69 | return ` ${from} --${arrowText} ${to}`; 70 | } 71 | } else { 72 | switch (type) { 73 | case "dotted": 74 | return ` ${from} -. ${text} .-${arrowText} ${to}`; 75 | case "thick": 76 | return ` ${from} == ${text} ==${arrowText} ${to}`; 77 | case "solid": 78 | default: 79 | return ` ${from} -- ${text} --${arrowText} ${to}`; 80 | } 81 | } 82 | } 83 | 84 | function transformGraphFragmentToMermaidString({ nodes, edges, subGraphs }) { 85 | const nodesMermaidString = fp.join( 86 | "\n", 87 | fp.map(transformNodeToMermaidString, nodes) 88 | ); 89 | const subGraphMermaidString = fp.join( 90 | "\n", 91 | fp.map(({ name, graph }) => { 92 | // console.log("SUBGRAPH", name); 93 | return ( 94 | `subgraph ${name.replace("_", " ")}\n` + 95 | transformGraphFragmentToMermaidString(graph) + 96 | `\nend` 97 | ); 98 | }, subGraphs) 99 | ); 100 | // console.log("subGraphMermaidString", subGraphMermaidString); 101 | 102 | const edgesMermaidString = fp.join( 103 | "\n", 104 | fp.map(transformEdgeToMermaidString, edges) 105 | ); 106 | 107 | return ( 108 | nodesMermaidString + 109 | (fp.isEmpty(subGraphs) ? "" : "\n" + subGraphMermaidString) + 110 | "\n" + 111 | edgesMermaidString 112 | ); 113 | } 114 | 115 | function transformGraphToMermaidString( 116 | { nodes, edges, subGraphs }, 117 | { direction = "TD" } = {} 118 | ) { 119 | const graphMermaidString = `graph ${direction}`; 120 | const graphFragmentMermaidString = transformGraphFragmentToMermaidString({ 121 | nodes, 122 | edges, 123 | subGraphs 124 | }); 125 | return graphMermaidString + "\n" + graphFragmentMermaidString; 126 | } 127 | 128 | function transformJsStringToMermaidString(string) { 129 | return transformGraphToMermaidString( 130 | transformJsAstToGraph(transformJsStringToJsAst(string)) 131 | ); 132 | } 133 | 134 | module.exports = { transformJsStringToMermaidString, transformJsStringToJsAst }; 135 | -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | transformJsStringToMermaidString, 3 | transformJsStringToJsAst 4 | } = require("."); 5 | 6 | test("1", () => { 7 | const res = transformJsStringToMermaidString("function f(x){return 5;}"); 8 | // console.log(res); 9 | expect(res).toEqual( 10 | `graph TD 11 | 12 | subgraph f 13 | statementfroml1c14tol1c23>"return 5;"] 14 | style statementfroml1c14tol1c23 fill:#99FF99 15 | 16 | end 17 | ` 18 | ); 19 | }); 20 | 21 | test("2", () => { 22 | const res = transformJsStringToMermaidString("(x)=>{return 5;}"); 23 | // console.log(res); 24 | expect(res).toEqual(`graph TD 25 | 26 | subgraph statementfroml1c0tol1c16 27 | statementfroml1c6tol1c15>"return 5;"] 28 | style statementfroml1c6tol1c15 fill:#99FF99 29 | 30 | end 31 | `); 32 | }); 33 | 34 | test("3", () => { 35 | const res = transformJsStringToMermaidString( 36 | "(x)=>{if(x===0){return 5;}else{return 4}}" 37 | ); 38 | // console.log(res); 39 | expect(res).toEqual(`graph TD 40 | 41 | subgraph statementfroml1c0tol1c41 42 | statementfroml1c6tol1c40{"if x === 0"} 43 | statementfroml1c16tol1c25>"return 5;"] 44 | style statementfroml1c16tol1c25 fill:#99FF99 45 | statementfroml1c31tol1c39>"return 4;"] 46 | style statementfroml1c31tol1c39 fill:#99FF99 47 | statementfroml1c6tol1c40 -- true --> statementfroml1c16tol1c25 48 | statementfroml1c6tol1c40 -- false --> statementfroml1c31tol1c39 49 | end 50 | `); 51 | }); 52 | 53 | test("4", () => { 54 | const res = transformJsStringToMermaidString( 55 | "(x)=>{const a=f(x);if(x===0){return 5;}else{const c=8;return 4}}" 56 | ); 57 | // console.log(res); 58 | 59 | expect(res).toEqual(`graph TD 60 | 61 | subgraph statementfroml1c0tol1c64 62 | statementfroml1c6tol1c19("const a = f(x);") 63 | statementfroml1c19tol1c63{"if x === 0"} 64 | statementfroml1c29tol1c38>"return 5;"] 65 | style statementfroml1c29tol1c38 fill:#99FF99 66 | statementfroml1c44tol1c54("const c = 8;") 67 | statementfroml1c54tol1c62>"return 4;"] 68 | style statementfroml1c54tol1c62 fill:#99FF99 69 | statementfroml1c19tol1c63 -- true --> statementfroml1c29tol1c38 70 | statementfroml1c19tol1c63 -- false --> statementfroml1c44tol1c54 71 | statementfroml1c44tol1c54 --> statementfroml1c54tol1c62 72 | statementfroml1c6tol1c19 --> statementfroml1c19tol1c63 73 | end 74 | `); 75 | }); 76 | 77 | test("5", () => { 78 | const res = transformJsStringToMermaidString( 79 | "const g = (x)=>{const a=f(x);if(x===0){return 5;}else{const c=8;return 4}}" 80 | ); 81 | // console.log(res); 82 | 83 | expect(res).toEqual(`graph TD 84 | 85 | subgraph g 86 | statementfroml1c16tol1c29("const a = f(x);") 87 | statementfroml1c29tol1c73{"if x === 0"} 88 | statementfroml1c39tol1c48>"return 5;"] 89 | style statementfroml1c39tol1c48 fill:#99FF99 90 | statementfroml1c54tol1c64("const c = 8;") 91 | statementfroml1c64tol1c72>"return 4;"] 92 | style statementfroml1c64tol1c72 fill:#99FF99 93 | statementfroml1c29tol1c73 -- true --> statementfroml1c39tol1c48 94 | statementfroml1c29tol1c73 -- false --> statementfroml1c54tol1c64 95 | statementfroml1c54tol1c64 --> statementfroml1c64tol1c72 96 | statementfroml1c16tol1c29 --> statementfroml1c29tol1c73 97 | end 98 | `); 99 | }); 100 | 101 | test("6", () => { 102 | const res = transformJsStringToMermaidString( 103 | "const g = function k(x){const a=w(x);if(x===0){return 6;}else{const c=8;return 4}}" 104 | ); 105 | // console.log(res); 106 | 107 | expect(res).toEqual(`graph TD 108 | 109 | subgraph g 110 | statementfroml1c24tol1c37("const a = w(x);") 111 | statementfroml1c37tol1c81{"if x === 0"} 112 | statementfroml1c47tol1c56>"return 6;"] 113 | style statementfroml1c47tol1c56 fill:#99FF99 114 | statementfroml1c62tol1c72("const c = 8;") 115 | statementfroml1c72tol1c80>"return 4;"] 116 | style statementfroml1c72tol1c80 fill:#99FF99 117 | statementfroml1c37tol1c81 -- true --> statementfroml1c47tol1c56 118 | statementfroml1c37tol1c81 -- false --> statementfroml1c62tol1c72 119 | statementfroml1c62tol1c72 --> statementfroml1c72tol1c80 120 | statementfroml1c24tol1c37 --> statementfroml1c37tol1c81 121 | end 122 | `); 123 | }); 124 | 125 | test("7", () => { 126 | const res = transformJsStringToMermaidString( 127 | `(x)=>{const a=f(x);if(x===0){let a=null; throw new Error("Nooes")}else{const c=8;return 4}}` 128 | ); 129 | // console.log(res); 130 | 131 | expect(res).toEqual(`graph TD 132 | 133 | subgraph statementfroml1c0tol1c91 134 | statementfroml1c6tol1c19("const a = f(x);") 135 | statementfroml1c19tol1c90{"if x === 0"} 136 | statementfroml1c29tol1c40("let a = null;") 137 | statementfroml1c41tol1c65>"throw new Error('Nooes');"] 138 | style statementfroml1c41tol1c65 fill:#FF9999 139 | statementfroml1c71tol1c81("const c = 8;") 140 | statementfroml1c81tol1c89>"return 4;"] 141 | style statementfroml1c81tol1c89 fill:#99FF99 142 | statementfroml1c19tol1c90 -- true --> statementfroml1c29tol1c40 143 | statementfroml1c19tol1c90 -- false --> statementfroml1c71tol1c81 144 | statementfroml1c29tol1c40 --> statementfroml1c41tol1c65 145 | statementfroml1c71tol1c81 --> statementfroml1c81tol1c89 146 | statementfroml1c6tol1c19 --> statementfroml1c19tol1c90 147 | end 148 | `); 149 | }); 150 | 151 | test("8", () => { 152 | const res = transformJsStringToMermaidString( 153 | `const q = (x)=>{switch(x){case 0: return 1;case 2:{const a=1;return 3;};default: return 4}}` 154 | ); 155 | // console.log(res); 156 | 157 | expect(res).toEqual(`graph TD 158 | 159 | subgraph q 160 | statementfroml1c16tol1c90{"switch x "} 161 | statementfroml1c34tol1c43>"return 1;"] 162 | style statementfroml1c34tol1c43 fill:#99FF99 163 | statementfroml1c51tol1c61("const a = 1;") 164 | statementfroml1c61tol1c70>"return 3;"] 165 | style statementfroml1c61tol1c70 fill:#99FF99 166 | statementfroml1c71tol1c72["Empty statement at line 1 column 71"] 167 | statementfroml1c81tol1c89>"return 4;"] 168 | style statementfroml1c81tol1c89 fill:#99FF99 169 | statementfroml1c16tol1c90 -- 0 --> statementfroml1c34tol1c43 170 | statementfroml1c51tol1c61 --> statementfroml1c61tol1c70 171 | statementfroml1c16tol1c90 -- 2 --> statementfroml1c51tol1c61 172 | statementfroml1c16tol1c90 -- default --> statementfroml1c81tol1c89 173 | statementfroml1c71tol1c72 --> statementfroml1c81tol1c89 174 | end 175 | `); 176 | }); 177 | 178 | test("9", () => { 179 | const res = transformJsStringToMermaidString( 180 | `const gogo = function(x){ 181 | let y = 9; 182 | switch(x){ 183 | case 0: throw new Error("coco"); 184 | case 1: return 1; 185 | case 2:{const a=98;return 3;}; 186 | case 3: y =5; 187 | case 4: y=9; break; 188 | default: return 4 189 | } 190 | console.log(y); 191 | return y + 1; 192 | }` 193 | ); 194 | // console.log(res); 195 | 196 | expect(res).toEqual(`graph TD 197 | 198 | subgraph gogo 199 | statementfroml2c10tol2c20("let y = 9;") 200 | statementfroml3c10tol10c11{"switch x "} 201 | statementfroml4c22tol4c46>"throw new Error('coco');"] 202 | style statementfroml4c22tol4c46 fill:#FF9999 203 | statementfroml5c22tol5c31>"return 1;"] 204 | style statementfroml5c22tol5c31 fill:#99FF99 205 | statementfroml6c22tol6c33("const a = 98;") 206 | statementfroml6c33tol6c42>"return 3;"] 207 | style statementfroml6c33tol6c42 fill:#99FF99 208 | statementfroml6c43tol6c44["Empty statement at line 6 column 43"] 209 | statementfroml7c22tol7c27("y = 5;") 210 | statementfroml8c22tol8c26("y = 9;") 211 | statementfroml8c27tol8c33["break;"] 212 | statementfroml9c23tol9c31>"return 4;"] 213 | style statementfroml9c23tol9c31 fill:#99FF99 214 | statementfroml11c10tol11c25("console.log(y);") 215 | statementfroml12c10tol12c23>"return y + 1;"] 216 | style statementfroml12c10tol12c23 fill:#99FF99 217 | statementfroml3c10tol10c11 -- 0 --> statementfroml4c22tol4c46 218 | statementfroml3c10tol10c11 -- 1 --> statementfroml5c22tol5c31 219 | statementfroml6c22tol6c33 --> statementfroml6c33tol6c42 220 | statementfroml3c10tol10c11 -- 2 --> statementfroml6c22tol6c33 221 | statementfroml3c10tol10c11 -- 3 --> statementfroml7c22tol7c27 222 | statementfroml6c43tol6c44 --> statementfroml7c22tol7c27 223 | statementfroml8c22tol8c26 --> statementfroml8c27tol8c33 224 | statementfroml3c10tol10c11 -- 4 --> statementfroml8c22tol8c26 225 | statementfroml7c22tol7c27 --> statementfroml8c22tol8c26 226 | statementfroml3c10tol10c11 -- default --> statementfroml9c23tol9c31 227 | statementfroml2c10tol2c20 --> statementfroml3c10tol10c11 228 | statementfroml8c27tol8c33 --> statementfroml11c10tol11c25 229 | statementfroml11c10tol11c25 --> statementfroml12c10tol12c23 230 | end 231 | `); 232 | }); 233 | -------------------------------------------------------------------------------- /mermaid-diagram-example.svg: -------------------------------------------------------------------------------- 1 |
true
false
const a = f(x);
x === 0
let a = null;
throw new Error('Nooes');
const c = 8;
return 4;
-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-to-graph", 3 | "version": "1.0.15", 4 | "main": "index.js", 5 | "repository": "https://github.com/crubier/code-to-graph.git", 6 | "author": "Vincent Lecrubier ", 7 | "license": "MIT", 8 | "scripts": { 9 | "test": "jest", 10 | "postpublish": "cd docs-source && yarn install && yarn docs" 11 | }, 12 | "bin": { 13 | "code-to-graph": "./cli.js" 14 | }, 15 | "dependencies": { 16 | "@babel/generator": "^7.2.2", 17 | "@babel/parser": "^7.2.3", 18 | "lodash": "^4.17.11", 19 | "mermaid": "^8.0.0", 20 | "opn": "^5.4.0" 21 | }, 22 | "devDependencies": { 23 | "jest": "^23.6.0" 24 | } 25 | } -------------------------------------------------------------------------------- /parser.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | transformJsStringToMermaidString, 3 | transformJsStringToJsAst 4 | } = require("."); 5 | 6 | describe("parser", () => { 7 | test("return", () => { 8 | expect( 9 | transformJsStringToJsAst("function f(x){return 5;}") 10 | ).toMatchSnapshot(); 11 | }); 12 | 13 | test("return", () => { 14 | expect( 15 | transformJsStringToJsAst("function f(x){const a = 5;}") 16 | ).toMatchSnapshot(); 17 | }); 18 | 19 | test("if else", () => { 20 | expect( 21 | transformJsStringToJsAst( 22 | "function f(x){if(x===5){return 0}else{return 1}}" 23 | ) 24 | ).toMatchSnapshot(); 25 | }); 26 | 27 | test("if else", () => { 28 | expect( 29 | transformJsStringToJsAst( 30 | "function f(x){if(x===5){return 0}else if(x===6){return 1}else {return 2}}" 31 | ) 32 | ).toMatchSnapshot(); 33 | }); 34 | 35 | test("switch", () => { 36 | expect( 37 | transformJsStringToJsAst( 38 | "function f(x){switch(x){case 1:return 2; default: break;}return 3;}" 39 | ) 40 | ).toMatchSnapshot(); 41 | }); 42 | 43 | test("switch", () => { 44 | expect( 45 | transformJsStringToJsAst( 46 | `(x)=>{switch(x){case 0: return 1;case 2:{const a=1;return 3;};default: return 4}}` 47 | ) 48 | ).toMatchSnapshot(); 49 | }); 50 | 51 | test("yield", () => { 52 | expect( 53 | transformJsStringToJsAst("function* f(x){yield 5;}") 54 | ).toMatchSnapshot(); 55 | }); 56 | 57 | test("const yield", () => { 58 | expect( 59 | transformJsStringToJsAst("function* f(x){const a = yield 5;}") 60 | ).toMatchSnapshot(); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /transform.js: -------------------------------------------------------------------------------- 1 | const { default: generate } = require("@babel/generator"); 2 | const fp = require("lodash/fp"); 3 | 4 | function makeIdFromAstNode(astNode) { 5 | return `froml${astNode.loc.start.line}c${astNode.loc.start.column}tol${ 6 | astNode.loc.end.line 7 | }c${astNode.loc.end.column}`; 8 | } 9 | 10 | function transformGeneralAstToGraph(ast) { 11 | if (fp.isArray(ast)) { 12 | return transformStatementSequenceToGraph(ast); 13 | } else if (fp.isObject(ast)) { 14 | return transformStatementToGraph(ast); 15 | } else { 16 | return transformStatementToGraph(ast); 17 | } 18 | } 19 | 20 | const emptyGraph = { 21 | nodes: [], 22 | edges: [], 23 | entryNodes: [], 24 | exitNodes: [], 25 | breakNodes: [], 26 | subGraphs: [] 27 | }; 28 | 29 | function transformStatementSequenceToGraph(statements) { 30 | return fp.reduce( 31 | ( 32 | { nodes, edges, entryNodes, exitNodes, breakNodes, subGraphs }, 33 | { 34 | nodes: currentNodes, 35 | edges: currentEdges, 36 | entryNodes: currentEntryNodes, 37 | exitNodes: currentExitNodes, 38 | breakNodes: currentBreakNodes, 39 | subGraphs: currentSubGraphs 40 | } 41 | ) => ({ 42 | nodes: [...nodes, ...currentNodes], 43 | edges: fp.compact([ 44 | ...edges, 45 | ...currentEdges, 46 | ...fp.flatten( 47 | fp.map( 48 | exitNode => 49 | fp.map( 50 | entryNode => ({ 51 | from: exitNode.id, 52 | to: entryNode.id, 53 | name: "", 54 | type: "solid", 55 | arrow: true 56 | }), 57 | currentEntryNodes 58 | ), 59 | exitNodes 60 | ) 61 | ) 62 | ]), 63 | entryNodes: fp.isEmpty(entryNodes) ? currentEntryNodes : entryNodes, 64 | exitNodes: currentExitNodes, 65 | breakNodes: [...breakNodes, ...currentBreakNodes], 66 | subGraphs: [...subGraphs, ...currentSubGraphs] 67 | }), 68 | { 69 | nodes: [], 70 | edges: [], 71 | entryNodes: [], 72 | exitNodes: [], 73 | breakNodes: [], 74 | subGraphs: [] 75 | }, 76 | fp.map(transformGeneralAstToGraph, statements) 77 | ); 78 | } 79 | 80 | function transformStatementToGraph(statement) { 81 | try { 82 | switch (statement.type) { 83 | case "TSTypeAliasDeclaration": { 84 | return emptyGraph; 85 | } 86 | case "CommentLine": { 87 | return emptyGraph; 88 | } 89 | case "File": { 90 | return transformGeneralAstToGraph(statement.program); 91 | } 92 | case "Program": { 93 | return transformGeneralAstToGraph(statement.body); 94 | } 95 | case "ClassProperty": { 96 | const node = cleanGraphNode({ 97 | id: makeIdFromAstNode(statement), 98 | name: generate(statement).code, 99 | shape: "round" 100 | }); 101 | return { 102 | nodes: [node], 103 | edges: [], 104 | entryNodes: [], 105 | exitNodes: [], 106 | breakNodes: [], 107 | subGraphs: [] 108 | }; 109 | } 110 | case "ClassDeclaration": { 111 | return { 112 | nodes: [], 113 | edges: [], 114 | entryNodes: [], 115 | exitNodes: [], 116 | breakNodes: [], 117 | subGraphs: [ 118 | { 119 | name: `class_${ 120 | fp.isNil(statement.id) 121 | ? makeIdFromAstNode(statement) 122 | : generate(statement.id).code 123 | }`, 124 | graph: transformGeneralAstToGraph(statement.body) 125 | } 126 | ] 127 | }; 128 | } 129 | case "ClassBody": { 130 | return transformGeneralAstToGraph(statement.body); 131 | } 132 | case "FunctionExpression": { 133 | return transformGeneralAstToGraph(statement.body); 134 | } 135 | case "ArrowFunctionExpression": { 136 | return transformGeneralAstToGraph(statement.body); 137 | } 138 | case "ClassMethod": { 139 | return { 140 | nodes: [], 141 | edges: [], 142 | entryNodes: [], 143 | exitNodes: [], 144 | breakNodes: [], 145 | subGraphs: [ 146 | { 147 | name: `method_${ 148 | fp.isNil(statement.key) 149 | ? makeIdFromAstNode(statement) 150 | : generate(statement.key).code 151 | }`, 152 | graph: transformGeneralAstToGraph(statement.body) 153 | } 154 | ] 155 | }; 156 | } 157 | case "FunctionDeclaration": { 158 | // console.log("FunctionDeclaration"); 159 | // console.log(statement); 160 | return { 161 | nodes: [], 162 | edges: [], 163 | entryNodes: [], 164 | exitNodes: [], 165 | breakNodes: [], 166 | subGraphs: [ 167 | { 168 | name: `function_${ 169 | fp.isNil(statement.id) 170 | ? makeIdFromAstNode(statement) 171 | : generate(statement.id).code 172 | }`, 173 | graph: transformGeneralAstToGraph(statement.body) 174 | } 175 | ] 176 | }; 177 | } 178 | case "BlockStatement": { 179 | return transformGeneralAstToGraph(statement.body); 180 | } 181 | 182 | case "VariableDeclarator": { 183 | // console.log("VariableDeclarator"); 184 | const node = cleanGraphNode({ 185 | id: makeIdFromAstNode(statement), 186 | name: generate(statement).code, 187 | shape: "round" 188 | }); 189 | return { 190 | nodes: [node], 191 | edges: [], 192 | entryNodes: [node], 193 | exitNodes: [node], 194 | breakNodes: [], 195 | subGraphs: [] 196 | }; 197 | } 198 | case "VariableDeclaration": { 199 | // console.log("VariableDeclaration"); 200 | // console.log(statement); 201 | 202 | const literalsDeclarators = fp.filter(declarator => { 203 | if (declarator.init.type === "FunctionExpression") { 204 | return false; 205 | } else if (declarator.init.type === "ArrowFunctionExpression") { 206 | return false; 207 | } else if (declarator.init.type === "YieldExpression") { 208 | return false; 209 | } else { 210 | return true; 211 | } 212 | }, statement.declarations); 213 | 214 | const literalsDeclarations = { 215 | ...statement, 216 | declarations: [...literalsDeclarators] 217 | }; 218 | 219 | const literalsNodes = fp.isEmpty(literalsDeclarations.declarations) 220 | ? [] 221 | : [ 222 | cleanGraphNode({ 223 | id: makeIdFromAstNode(literalsDeclarations), 224 | name: generate(literalsDeclarations).code, 225 | shape: "round" 226 | }) 227 | ]; 228 | 229 | const functionsDeclarators = fp.filter(declarator => { 230 | if (declarator.init.type === "FunctionExpression") { 231 | return true; 232 | } else if (declarator.init.type === "ArrowFunctionExpression") { 233 | return true; 234 | } else if (declarator.init.type === "YieldExpression") { 235 | return false; 236 | } else { 237 | return false; 238 | } 239 | }, statement.declarations); 240 | 241 | // console.log("functionsDeclarators", functionsDeclarators); 242 | 243 | const functionsSubGraphs = fp.reduce( 244 | ({ subGraphs }, declarator) => { 245 | return { 246 | subGraphs: [ 247 | ...subGraphs, 248 | { 249 | name: `function_${declarator.id.name}`, 250 | graph: transformGeneralAstToGraph(declarator.init) 251 | } 252 | ] 253 | }; 254 | }, 255 | { 256 | subGraphs: [] 257 | }, 258 | functionsDeclarators 259 | ); 260 | 261 | const yieldDeclarators = fp.filter(declarator => { 262 | if (declarator.init.type === "FunctionExpression") { 263 | return false; 264 | } else if (declarator.init.type === "ArrowFunctionExpression") { 265 | return false; 266 | } else if (declarator.init.type === "YieldExpression") { 267 | // console.log("YIELLLDD"); 268 | // console.log(declarator); 269 | // console.log(""); 270 | return true; 271 | } else { 272 | return false; 273 | } 274 | }, statement.declarations); 275 | 276 | const yieldDeclarations = { 277 | ...statement, 278 | declarations: [...yieldDeclarators] 279 | }; 280 | 281 | const yieldNodes = fp.isEmpty(yieldDeclarations.declarations) 282 | ? [] 283 | : [ 284 | cleanGraphNode({ 285 | id: makeIdFromAstNode(statement), 286 | name: generate(statement).code, 287 | shape: "asymetric", 288 | style: { fill: "#99CCFF" } 289 | }) 290 | ]; 291 | 292 | return { 293 | nodes: [...literalsNodes, ...yieldNodes], 294 | edges: [], 295 | entryNodes: [...literalsNodes, ...yieldNodes], 296 | exitNodes: [...literalsNodes, ...yieldNodes], 297 | breakNodes: [], 298 | subGraphs: [...functionsSubGraphs.subGraphs] 299 | }; 300 | } 301 | case "ExpressionStatement": { 302 | if (statement.expression.type === "ArrowFunctionExpression") { 303 | return { 304 | nodes: [], 305 | edges: [], 306 | entryNodes: [], 307 | exitNodes: [], 308 | breakNodes: [], 309 | subGraphs: [ 310 | { 311 | name: `function_${makeIdFromAstNode(statement)}`, 312 | graph: transformGeneralAstToGraph(statement.expression.body) 313 | } 314 | ] 315 | }; 316 | } else if (statement.expression.type === "FunctionExpression") { 317 | // console.log("FunctionExpression"); 318 | return { 319 | nodes: [], 320 | edges: [], 321 | entryNodes: [], 322 | exitNodes: [], 323 | breakNodes: [], 324 | subGraphs: [ 325 | { 326 | name: `function_${statement.name}`, 327 | graph: transformGeneralAstToGraph(statement.body) 328 | } 329 | ] 330 | }; 331 | } else if (statement.expression.type === "YieldExpression") { 332 | const node = cleanGraphNode({ 333 | id: makeIdFromAstNode(statement), 334 | name: generate(statement).code, 335 | shape: "asymetric", 336 | style: { fill: "#99CCFF" } 337 | }); 338 | return { 339 | nodes: [node], 340 | edges: [], 341 | entryNodes: [node], 342 | exitNodes: [node], 343 | breakNodes: [], 344 | subGraphs: [] 345 | }; 346 | } else { 347 | const node = cleanGraphNode({ 348 | id: makeIdFromAstNode(statement), 349 | name: generate(statement).code, 350 | shape: "round" 351 | }); 352 | return { 353 | nodes: [node], 354 | edges: [], 355 | entryNodes: [node], 356 | exitNodes: [node], 357 | breakNodes: [], 358 | subGraphs: [] 359 | }; 360 | } 361 | } 362 | case "CatchClause": { 363 | return transformGeneralAstToGraph(statement.body); 364 | } 365 | case "TryStatement": { 366 | // statement.finalizer; 367 | // statement.handler; 368 | // statement.block; 369 | const { 370 | nodes: blockNodes, 371 | edges: blockEdges, 372 | entryNodes: blockEntryNodes, 373 | exitNodes: blockExitNodes, 374 | breakNodes: blockBreakNodes, 375 | subGraphs: blockSubGraphs 376 | } = fp.isObject(statement.block) 377 | ? transformGeneralAstToGraph(statement.block) 378 | : emptyGraph; 379 | const { 380 | nodes: finalizerNodes, 381 | edges: finalizerEdges, 382 | entryNodes: finalizerEntryNodes, 383 | exitNodes: finalizerExitNodes, 384 | breakNodes: finalizerBreakNodes, 385 | subGraphs: finalizerSubGraphs 386 | } = fp.isObject(statement.finalizer) 387 | ? transformGeneralAstToGraph(statement.finalizer) 388 | : emptyGraph; 389 | const { 390 | nodes: handlerNodes, 391 | edges: handlerEdges, 392 | entryNodes: handlerEntryNodes, 393 | exitNodes: handlerExitNodes, 394 | breakNodes: handlerBreakNodes, 395 | subGraphs: handlerSubGraphs 396 | } = fp.isObject(statement.handler) 397 | ? transformGeneralAstToGraph(statement.handler) 398 | : emptyGraph; 399 | const blockToFinallyEdges = fp.isObject(statement.finalizer) 400 | ? fp.flatten( 401 | fp.map(blockExitNode => { 402 | return fp.map(finalizerEntryNode => { 403 | // console.log("blockExitNode", blockExitNode); 404 | // console.log("finalizerEntryNode", finalizerEntryNode); 405 | return { 406 | from: blockExitNode.id, 407 | to: finalizerEntryNode.id, 408 | name: "", 409 | type: "solid", 410 | arrow: true 411 | }; 412 | }, finalizerEntryNodes); 413 | }, blockExitNodes) 414 | ) 415 | : []; 416 | const handlerToFinallyEdges = fp.isObject(statement.finalizer) 417 | ? fp.flatten( 418 | fp.map(handlerExitNode => { 419 | return fp.map(finalizerEntryNode => { 420 | // console.log("handlerExitNode", handlerExitNode); 421 | // console.log("finalizerEntryNode", finalizerEntryNode); 422 | return { 423 | from: handlerExitNode.id, 424 | to: finalizerEntryNode.id, 425 | name: "", 426 | type: "solid", 427 | arrow: true 428 | }; 429 | }, finalizerEntryNodes); 430 | }, handlerExitNodes) 431 | ) 432 | : []; 433 | const blockToHandlerEdges = fp.isObject(statement.handler) 434 | ? fp.flatten( 435 | fp.map(blockNode => { 436 | return fp.map(handlerEntryNode => { 437 | // console.log("blockNode", blockNode); 438 | // console.log("handlerEntryNode", handlerEntryNode); 439 | return { 440 | from: blockNode.id, 441 | to: handlerEntryNode.id, 442 | name: "error", 443 | type: "dotted", 444 | arrow: true 445 | }; 446 | }, handlerEntryNodes); 447 | }, blockNodes) 448 | ) 449 | : []; 450 | // console.log("blockToFinallyEdges", blockToFinallyEdges); 451 | return { 452 | nodes: [], 453 | edges: [ 454 | ...blockToFinallyEdges, 455 | ...handlerToFinallyEdges, 456 | ...blockToHandlerEdges 457 | ], 458 | entryNodes: [...blockEntryNodes], 459 | exitNodes: fp.isObject(statement.finalizer) 460 | ? [...finalizerExitNodes] 461 | : [...blockExitNodes], 462 | breakNodes: [...finalizerBreakNodes, ...blockBreakNodes], 463 | subGraphs: [ 464 | ...(fp.isObject(statement.block) 465 | ? [ 466 | { 467 | name: `try_${makeIdFromAstNode(statement.block)}`, 468 | graph: { 469 | nodes: blockNodes, 470 | edges: blockEdges, 471 | entryNodes: blockEntryNodes, 472 | exitNodes: blockExitNodes, 473 | breakNodes: blockBreakNodes, 474 | subGraphs: blockSubGraphs 475 | } 476 | } 477 | ] 478 | : []), 479 | ...(fp.isObject(statement.finalizer) 480 | ? [ 481 | { 482 | name: `finally_${makeIdFromAstNode(statement.finalizer)}`, 483 | graph: { 484 | nodes: finalizerNodes, 485 | edges: finalizerEdges, 486 | entryNodes: finalizerEntryNodes, 487 | exitNodes: finalizerExitNodes, 488 | breakNodes: finalizerBreakNodes, 489 | subGraphs: finalizerSubGraphs 490 | } 491 | } 492 | ] 493 | : []), 494 | ...(fp.isObject(statement.handler) 495 | ? [ 496 | { 497 | name: `catch_${makeIdFromAstNode(statement.handler)}`, 498 | graph: { 499 | nodes: handlerNodes, 500 | edges: handlerEdges, 501 | entryNodes: handlerEntryNodes, 502 | exitNodes: handlerExitNodes, 503 | breakNodes: handlerBreakNodes, 504 | subGraphs: handlerSubGraphs 505 | } 506 | } 507 | ] 508 | : []) 509 | ] 510 | }; 511 | } 512 | case "ExportNamedDeclaration": 513 | return transformGeneralAstToGraph(statement.declaration); 514 | case "ExportDefaultDeclaration": 515 | return transformGeneralAstToGraph(statement.declaration); 516 | case "Identifier": 517 | return { 518 | nodes: [], 519 | edges: [], 520 | entryNodes: [], 521 | exitNodes: [], 522 | breakNodes: [], 523 | subGraphs: [] 524 | }; 525 | case "ImportDeclaration": 526 | return { 527 | nodes: [], 528 | edges: [], 529 | entryNodes: [], 530 | exitNodes: [], 531 | breakNodes: [], 532 | subGraphs: [] 533 | }; 534 | case "EmptyStatement": { 535 | const node = cleanGraphNode({ 536 | id: makeIdFromAstNode(statement), 537 | name: `Empty statement at line ${statement.loc.start.line} column ${ 538 | statement.loc.start.column 539 | }`, 540 | shape: "square" 541 | }); 542 | return { 543 | nodes: [node], 544 | edges: [], 545 | entryNodes: [node], 546 | exitNodes: [node], 547 | breakNodes: [], 548 | subGraphs: [] 549 | }; 550 | } 551 | case "ReturnStatement": { 552 | const node = cleanGraphNode({ 553 | id: makeIdFromAstNode(statement), 554 | name: generate(statement).code, 555 | shape: "asymetric", 556 | style: { fill: "#99FF99" } 557 | }); 558 | return { 559 | nodes: [node], 560 | edges: [], 561 | entryNodes: [node], 562 | exitNodes: [], 563 | breakNodes: [], 564 | subGraphs: [] 565 | }; 566 | } 567 | case "ThrowStatement": { 568 | const node = cleanGraphNode({ 569 | id: makeIdFromAstNode(statement), 570 | name: generate(statement).code, 571 | shape: "asymetric", 572 | style: { fill: "#FF9999" } 573 | }); 574 | 575 | return { 576 | nodes: [node], 577 | edges: [], 578 | entryNodes: [node], 579 | exitNodes: [], 580 | breakNodes: [], 581 | subGraphs: [] 582 | }; 583 | } 584 | case "BreakStatement": { 585 | const node = cleanGraphNode({ 586 | id: makeIdFromAstNode(statement), 587 | name: generate(statement).code, 588 | shape: "square" 589 | }); 590 | return { 591 | nodes: [node], 592 | edges: [], 593 | entryNodes: [node], 594 | exitNodes: [], 595 | breakNodes: [node], 596 | subGraphs: [] 597 | }; 598 | } 599 | case "DoWhileStatement": 600 | const thisNode = cleanGraphNode({ 601 | id: makeIdFromAstNode(statement), 602 | name: `do while ${generate(statement.test).code}`, 603 | shape: "rhombus" 604 | }); 605 | const { 606 | nodes: bodyNodes, 607 | edges: bodyEdges, 608 | entryNodes: bodyEntryNodes, 609 | exitNodes: bodyExitNodes, 610 | breakNodes: bodyBreakNodes 611 | } = transformGeneralAstToGraph(statement.body); 612 | 613 | const thisEdges = [ 614 | ...fp.map( 615 | node => ({ 616 | to: thisNode.id, 617 | from: node.id, 618 | name: "do", 619 | type: "solid", 620 | arrow: true 621 | }), 622 | bodyExitNodes 623 | ), 624 | ...fp.map( 625 | node => ({ 626 | to: node.id, 627 | from: thisNode.id, 628 | name: "loop", 629 | type: "solid", 630 | arrow: true 631 | }), 632 | bodyEntryNodes 633 | ) 634 | ]; 635 | return { 636 | nodes: [thisNode, ...bodyNodes], 637 | edges: [...thisEdges, ...bodyEdges], 638 | entryNodes: [...bodyEntryNodes], 639 | exitNodes: [...bodyBreakNodes, thisNode], 640 | breakNodes: [], 641 | subGraphs: [] 642 | }; 643 | case "WhileStatement": { 644 | const thisNode = cleanGraphNode({ 645 | id: makeIdFromAstNode(statement), 646 | name: `while ${generate(statement.test).code}`, 647 | shape: "rhombus" 648 | }); 649 | const { 650 | nodes: bodyNodes, 651 | edges: bodyEdges, 652 | entryNodes: bodyEntryNodes, 653 | exitNodes: bodyExitNodes, 654 | breakNodes: bodyBreakNodes 655 | } = transformGeneralAstToGraph(statement.body); 656 | 657 | const thisEdges = [ 658 | ...fp.map( 659 | node => ({ 660 | from: thisNode.id, 661 | to: node.id, 662 | name: "do", 663 | type: "solid", 664 | arrow: true 665 | }), 666 | bodyEntryNodes 667 | ), 668 | ...fp.map( 669 | node => ({ 670 | from: node.id, 671 | to: thisNode.id, 672 | name: "loop", 673 | type: "solid", 674 | arrow: true 675 | }), 676 | bodyExitNodes 677 | ) 678 | ]; 679 | return { 680 | nodes: [thisNode, ...bodyNodes], 681 | edges: [...thisEdges, ...bodyEdges], 682 | entryNodes: [thisNode], 683 | exitNodes: [...bodyBreakNodes, thisNode], 684 | breakNodes: [], 685 | subGraphs: [] 686 | }; 687 | } 688 | case "ForOfStatement": 689 | case "ForStatement": { 690 | const thisNode = cleanGraphNode({ 691 | id: makeIdFromAstNode(statement), 692 | name: `for ${generate(statement.init).code} ; ${ 693 | generate(statement.test).code 694 | } ; ${generate(statement.update).code}`, 695 | shape: "rhombus" 696 | }); 697 | const { 698 | nodes: bodyNodes, 699 | edges: bodyEdges, 700 | entryNodes: bodyEntryNodes, 701 | exitNodes: bodyExitNodes, 702 | breakNodes: bodyBreakNodes 703 | } = transformGeneralAstToGraph(statement.body); 704 | 705 | const thisEdges = [ 706 | ...fp.map( 707 | node => ({ 708 | from: thisNode.id, 709 | to: node.id, 710 | name: "do", 711 | type: "solid", 712 | arrow: true 713 | }), 714 | bodyEntryNodes 715 | ), 716 | ...fp.map( 717 | node => ({ 718 | from: node.id, 719 | to: thisNode.id, 720 | name: "loop", 721 | type: "solid", 722 | arrow: true 723 | }), 724 | bodyExitNodes 725 | ) 726 | ]; 727 | return { 728 | nodes: [thisNode, ...bodyNodes], 729 | edges: [...thisEdges, ...bodyEdges], 730 | entryNodes: [thisNode], 731 | exitNodes: [...bodyBreakNodes, thisNode], 732 | breakNodes: [], 733 | subGraphs: [] 734 | }; 735 | } 736 | case "IfStatement": { 737 | const thisNode = cleanGraphNode({ 738 | id: makeIdFromAstNode(statement), 739 | name: `if ${generate(statement.test).code}`, 740 | shape: "rhombus" 741 | }); 742 | const { 743 | nodes: consequentNodes, 744 | edges: consequentEdges, 745 | entryNodes: consequentEntryNodes, 746 | exitNodes: consequentExitNodes, 747 | breakNodes: consequentBreakNodes 748 | } = transformGeneralAstToGraph(statement.consequent); 749 | const { 750 | nodes: alternateNodes, 751 | edges: alternateEdges, 752 | entryNodes: alternateEntryNodes, 753 | exitNodes: alternateExitNodes, 754 | breakNodes: alternateBreakNodes 755 | } = !fp.isNil(statement.alternate) 756 | ? transformGeneralAstToGraph(statement.alternate) 757 | : { 758 | nodes: [], 759 | edges: [], 760 | entryNodes: [], 761 | exitNodes: [thisNode], 762 | breakNodes: [] 763 | }; 764 | 765 | const thisEdges = [ 766 | ...fp.map( 767 | node => ({ 768 | from: thisNode.id, 769 | to: node.id, 770 | name: "true", 771 | type: "solid", 772 | arrow: true 773 | }), 774 | consequentEntryNodes 775 | ), 776 | ...fp.map( 777 | node => ({ 778 | from: thisNode.id, 779 | to: node.id, 780 | name: "false", 781 | type: "solid", 782 | arrow: true 783 | }), 784 | alternateEntryNodes 785 | ) 786 | ]; 787 | return { 788 | nodes: [thisNode, ...consequentNodes, ...alternateNodes], 789 | edges: [...thisEdges, ...consequentEdges, ...alternateEdges], 790 | entryNodes: [thisNode], 791 | exitNodes: [...consequentExitNodes, ...alternateExitNodes], 792 | breakNodes: [...consequentBreakNodes, ...alternateBreakNodes], 793 | subGraphs: [] 794 | }; 795 | } 796 | case "SwitchStatement": 797 | { 798 | const thisNode = cleanGraphNode({ 799 | id: makeIdFromAstNode(statement), 800 | name: `switch ${generate(statement.discriminant).code} `, 801 | shape: "rhombus" 802 | }); 803 | 804 | const scopeGraph = fp.reduce( 805 | ( 806 | { nodes, edges, entryNodes, exitNodes, breakNodes }, 807 | caseAstElement 808 | ) => { 809 | const { 810 | nodes: caseNodes, 811 | edges: caseEdges, 812 | entryNodes: caseEntryNodes, 813 | exitNodes: caseExitNodes, 814 | breakNodes: caseBreakNodes 815 | } = transformGeneralAstToGraph(caseAstElement.consequent); 816 | const caseEntryEdges = fp.map( 817 | node => ({ 818 | from: thisNode.id, 819 | to: node.id, 820 | name: fp.isEmpty(caseAstElement.test) 821 | ? "default" 822 | : generate(caseAstElement.test).code, 823 | type: "solid", 824 | arrow: true 825 | }), 826 | caseEntryNodes 827 | ); 828 | const caseFollowEdges = fp.flatten( 829 | fp.map( 830 | exitNode => 831 | fp.map( 832 | entryNode => ({ 833 | from: exitNode.id, 834 | to: entryNode.id, 835 | name: "", 836 | type: "solid", 837 | arrow: true 838 | }), 839 | caseEntryNodes 840 | ), 841 | exitNodes 842 | ) 843 | ); 844 | // console.log("caseNodes", caseNodes); 845 | return { 846 | nodes: [...nodes, ...caseNodes], 847 | edges: fp.compact([ 848 | ...edges, 849 | ...caseEdges, 850 | ...caseEntryEdges, 851 | ...caseFollowEdges 852 | ]), 853 | entryNodes: [...entryNodes], 854 | exitNodes: [...caseExitNodes], 855 | breakNodes: [...breakNodes, ...caseBreakNodes] 856 | }; 857 | }, 858 | { 859 | nodes: [thisNode], 860 | edges: [], 861 | entryNodes: [thisNode], 862 | exitNodes: [], 863 | breakNodes: [] 864 | }, 865 | statement.cases 866 | ); 867 | 868 | return { 869 | nodes: [...scopeGraph.nodes], 870 | edges: [...scopeGraph.edges], 871 | entryNodes: [...scopeGraph.entryNodes], 872 | exitNodes: [...scopeGraph.breakNodes], 873 | breakNodes: [], 874 | subGraphs: [] 875 | }; 876 | } 877 | FunctionDeclaration; 878 | default: 879 | throw new Error( 880 | `Statements of type ${statement.type} are not yet supported, from ${ 881 | statement.loc.start.line 882 | }:${statement.loc.start.column} to ${statement.loc.end.line}:${ 883 | statement.loc.end.column 884 | }` 885 | ); 886 | } 887 | } catch (error) { 888 | console.log("statement"); 889 | console.log(statement); 890 | throw error; 891 | } 892 | } 893 | 894 | function cleanGraphNode({ name, ...rest }) { 895 | return { 896 | ...rest, 897 | name: `"${fp.compose([fp.replace(/(\")/g, "'")])(name)}"` 898 | }; 899 | } 900 | 901 | module.exports = { transformGeneralAstToGraph }; 902 | --------------------------------------------------------------------------------