#![cfg(feature = "js")]
use pyrograph::analyze;
fn must_be_clean(js: &str, n: &str) {
let g = pyrograph::parse::parse_js(js, &format!("{n}.js")).unwrap();
assert!(analyze(&g).unwrap().is_empty(), "{n}");
}
#[test]
fn fp_lodash_get() {
let js = r#"
const _ = require('lodash');
const obj = { 'a': [{ 'b': { 'c': 3 } }] };
const val = _.get(obj, 'a[0].b.c');
console.log(val);
"#;
must_be_clean(js, "lodash_get");
}
#[test]
fn fp_lodash_set() {
let js = r#"
const _ = require('lodash');
const obj = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(obj, 'a[0].b.c', 4);
console.log(obj.a[0].b.c);
"#;
must_be_clean(js, "lodash_set");
}
#[test]
fn fp_lodash_merge() {
let js = r#"
const _ = require('lodash');
const obj = { 'a': [{ 'b': 2 }, { 'd': 4 }] };
const other = { 'a': [{ 'c': 3 }, { 'e': 5 }] };
_.merge(obj, other);
"#;
must_be_clean(js, "lodash_merge");
}
#[test]
fn fp_lodash_clone_deep() {
let js = r#"
const _ = require('lodash');
const objects = [{ 'a': 1 }, { 'b': 2 }];
const deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
"#;
must_be_clean(js, "lodash_clone_deep");
}
#[test]
fn fp_lodash_debounce() {
let js = r#"
const _ = require('lodash');
const log = () => console.log('Debounced!');
const debouncedLog = _.debounce(log, 250);
debouncedLog();
"#;
must_be_clean(js, "lodash_debounce");
}
#[test]
fn fp_express_basic_route() {
let js = r#"
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);
"#;
must_be_clean(js, "express_basic_route");
}
#[test]
fn fp_express_middleware() {
let js = r#"
const express = require('express');
const app = express();
const logger = (req, res, next) => {
console.log(req.method, req.url);
next();
};
app.use(logger);
app.get('/api', (req, res) => res.json({ status: 'ok' }));
"#;
must_be_clean(js, "express_middleware");
}
#[test]
fn fp_express_json_body() {
let js = r#"
const express = require('express');
const app = express();
app.use(express.json());
app.post('/data', (req, res) => {
console.log(req.body);
res.status(201).send();
});
"#;
must_be_clean(js, "express_json_body");
}
#[test]
fn fp_express_router() {
let js = r#"
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => res.json([{id: 1}]));
const app = express();
app.use('/api', router);
"#;
must_be_clean(js, "express_router");
}
#[test]
fn fp_express_error_handler() {
let js = r#"
const express = require('express');
const app = express();
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
"#;
must_be_clean(js, "express_error_handler");
}
#[test]
fn fp_react_use_state() {
let js = r#"
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
"#;
must_be_clean(js, "react_use_state");
}
#[test]
fn fp_react_use_effect() {
let js = r#"
import React, { useState, useEffect } from 'react';
function TitleUpdater() {
const [title, setTitle] = useState('Hello');
useEffect(() => {
document.title = title;
}, [title]);
return <input value={title} onChange={e => setTitle(e.target.value)} />;
}
"#;
must_be_clean(js, "react_use_effect");
}
#[test]
fn fp_react_use_callback() {
let js = r#"
import React, { useState, useCallback } from 'react';
function MemoizedBtn() {
const [val, setVal] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked', val);
}, [val]);
return <button onClick={handleClick}>Click</button>;
}
"#;
must_be_clean(js, "react_use_callback");
}
#[test]
fn fp_react_use_memo() {
let js = r#"
import React, { useMemo } from 'react';
function ExpensiveCalc({ num }) {
const result = useMemo(() => {
return num * 2; // pretend this is expensive
}, [num]);
return <div>{result}</div>;
}
"#;
must_be_clean(js, "react_use_memo");
}
#[test]
fn fp_react_custom_hook() {
let js = r#"
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
"#;
must_be_clean(js, "react_custom_hook");
}
#[test]
fn fp_axios_get() {
let js = r#"
const axios = require('axios');
axios.get('https://api.github.com/users/octocat')
.then(res => console.log(res.data))
.catch(err => console.error(err));
"#;
must_be_clean(js, "axios_get");
}
#[test]
fn fp_axios_post() {
let js = r#"
const axios = require('axios');
async function createUser() {
const res = await axios.post('https://jsonplaceholder.typicode.com/users', {
name: 'Alice',
email: 'alice@example.com'
});
return res.data;
}
"#;
must_be_clean(js, "axios_post");
}
#[test]
fn fp_axios_interceptor() {
let js = r#"
const axios = require('axios');
axios.interceptors.request.use(config => {
config.headers['Authorization'] = 'Bearer token';
return config;
});
axios.get('/api/me');
"#;
must_be_clean(js, "axios_interceptor");
}
#[test]
fn fp_fetch_get() {
let js = r#"
fetch('https://api.example.com/data.json')
.then(res => res.json())
.then(data => console.log(data));
"#;
must_be_clean(js, "fetch_get");
}
#[test]
fn fp_fetch_post() {
let js = r#"
async function sendData(data) {
const res = await fetch('https://api.example.com/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
return res.json();
}
"#;
must_be_clean(js, "fetch_post");
}
#[test]
fn fp_webpack_basic_config() {
let js = r#"
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
"#;
must_be_clean(js, "webpack_basic_config");
}
#[test]
fn fp_webpack_html_plugin() {
let js = r#"
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' })
]
};
"#;
must_be_clean(js, "webpack_html_plugin");
}
#[test]
fn fp_webpack_dev_server() {
let js = r#"
module.exports = {
devServer: {
static: './dist',
port: 8080,
hot: true
}
};
"#;
must_be_clean(js, "webpack_dev_server");
}
#[test]
fn fp_vite_basic_config() {
let js = r#"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: { port: 3000 }
});
"#;
must_be_clean(js, "vite_basic_config");
}
#[test]
fn fp_vite_build_options() {
let js = r#"
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'build',
sourcemap: true,
minify: 'terser'
}
});
"#;
must_be_clean(js, "vite_build_options");
}
#[test]
fn fp_jest_basic_test() {
let js = r#"
const sum = require('./sum');
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
"#;
must_be_clean(js, "jest_basic_test");
}
#[test]
fn fp_jest_mock_module() {
let js = r#"
const axios = require('axios');
jest.mock('axios');
test('fetches successfully data from an API', async () => {
const data = { data: { hits: [{ objectID: '1', title: 'a' }] } };
axios.get.mockImplementationOnce(() => Promise.resolve(data));
});
"#;
must_be_clean(js, "jest_mock_module");
}
#[test]
fn fp_jest_spy_on() {
let js = r#"
const video = require('./video');
test('plays video', () => {
const spy = jest.spyOn(video, 'play');
const isPlaying = video.play();
expect(spy).toHaveBeenCalled();
expect(isPlaying).toBe(true);
spy.mockRestore();
});
"#;
must_be_clean(js, "jest_spy_on");
}
#[test]
fn fp_jest_before_after() {
let js = r#"
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
"#;
must_be_clean(js, "jest_before_after");
}
#[test]
fn fp_jest_snapshot() {
let js = r#"
it('renders correctly', () => {
const tree = renderer.create(<Link page="http://www.facebook.com">Facebook</Link>).toJSON();
expect(tree).toMatchSnapshot();
});
"#;
must_be_clean(js, "jest_snapshot");
}
#[test]
fn fp_ts_create_program() {
let js = r#"
const ts = require('typescript');
function compile(fileNames, options) {
let program = ts.createProgram(fileNames, options);
let emitResult = program.emit();
let allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
}
"#;
must_be_clean(js, "ts_create_program");
}
#[test]
fn fp_ts_parse_config() {
let js = r#"
const ts = require('typescript');
const configPath = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json');
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedCommandLine = ts.parseJsonConfigFileContent(configFile.config, ts.sys, './');
"#;
must_be_clean(js, "ts_parse_config");
}
#[test]
fn fp_ts_transform() {
let js = r#"
const ts = require('typescript');
const source = 'let x: string = "string"';
let result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS }});
console.log(result.outputText);
"#;
must_be_clean(js, "ts_transform");
}
#[test]
fn fp_ts_ast_visitor() {
let js = r#"
const ts = require('typescript');
function visit(node) {
if (ts.isFunctionDeclaration(node)) {
console.log(node.name.text);
}
ts.forEachChild(node, visit);
}
"#;
must_be_clean(js, "ts_ast_visitor");
}
#[test]
fn fp_ts_printer() {
let js = r#"
const ts = require('typescript');
const file = ts.createSourceFile('source.ts', '', ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
const result = printer.printNode(ts.EmitHint.Unspecified, file, file);
"#;
must_be_clean(js, "ts_printer");
}
#[test]
fn fp_commander_basic() {
let js = r#"
const { program } = require('commander');
program
.version('0.1.0')
.option('-p, --peppers', 'Add peppers')
.option('-c, --cheese <type>', 'Add the specified type of cheese', 'blue');
program.parse(process.argv);
"#;
must_be_clean(js, "commander_basic");
}
#[test]
fn fp_commander_action() {
let js = r#"
const { Command } = require('commander');
const program = new Command();
program
.command('clone <source> [destination]')
.description('clone a repository into a newly created directory')
.action((source, destination) => {
console.log('clone command called');
});
program.parse(process.argv);
"#;
must_be_clean(js, "commander_action");
}
#[test]
fn fp_yargs_basic() {
let js = r#"
const yargs = require('yargs/yargs');
const { hideBin } = require('yargs/helpers');
const argv = yargs(hideBin(process.argv)).argv;
if (argv.ships > 3 && argv.distance < 53.5) {
console.log('Plunder more rife!');
}
"#;
must_be_clean(js, "yargs_basic");
}
#[test]
fn fp_yargs_command() {
let js = r#"
require('yargs')
.scriptName('pirate-parser')
.usage('$0 <cmd> [args]')
.command('hello [name]', 'welcome ter yargs!', (yargs) => {
yargs.positional('name', { type: 'string', default: 'Cambi', describe: 'the name to say hello to' })
}, function (argv) {
console.log('hello', argv.name, 'welcome to yargs!')
})
.help()
.argv;
"#;
must_be_clean(js, "yargs_command");
}
#[test]
fn fp_yargs_options() {
let js = r#"
const argv = require('yargs')
.option('f', {
alias: 'file',
demandOption: true,
default: '/etc/passwd',
describe: 'x marks the spot',
type: 'string'
})
.argv;
"#;
must_be_clean(js, "yargs_options");
}
#[test]
fn fp_winston_basic() {
let js = r#"
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
logger.info('Hello again distributed logs');
"#;
must_be_clean(js, "winston_basic");
}
#[test]
fn fp_winston_console() {
let js = r#"
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.simple(),
transports: [new winston.transports.Console()]
});
logger.error('error message');
"#;
must_be_clean(js, "winston_console");
}
#[test]
fn fp_pino_basic() {
let js = r#"
const logger = require('pino')()
logger.info('hello world')
const child = logger.child({ a: 'property' })
child.info('hello child!')
"#;
must_be_clean(js, "pino_basic");
}
#[test]
fn fp_pino_error() {
let js = r#"
const pino = require('pino');
const logger = pino();
try {
throw new Error('Some error');
} catch (err) {
logger.error(err, 'Caught an error');
}
"#;
must_be_clean(js, "pino_error");
}
#[test]
fn fp_pino_http() {
let js = r#"
const pino = require('pino')()
const pinoHttp = require('pino-http')({ logger: pino })
const http = require('http')
http.createServer(function (req, res) {
pinoHttp(req, res)
res.end('hello world')
}).listen(3000)
"#;
must_be_clean(js, "pino_http");
}
#[test]
fn fp_dotenv_config() {
let js = r#"
require('dotenv').config();
console.log(process.env.DB_HOST);
console.log(process.env.DB_USER);
"#;
must_be_clean(js, "dotenv_config");
}
#[test]
fn fp_dotenv_custom_path() {
let js = r#"
require('dotenv').config({ path: '/custom/path/to/.env' });
const port = process.env.PORT || 3000;
console.log(`Listening on port ${port}`);
"#;
must_be_clean(js, "dotenv_custom_path");
}
#[test]
fn fp_dotenv_parse() {
let js = r#"
const dotenv = require('dotenv');
const buf = Buffer.from('BASIC=basic');
const config = dotenv.parse(buf);
console.log(typeof config, config);
"#;
must_be_clean(js, "dotenv_parse");
}
#[test]
fn fp_dotenv_expand() {
let js = r#"
const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand');
const myEnv = dotenv.config();
dotenvExpand.expand(myEnv);
console.log(process.env.EXPANDED);
"#;
must_be_clean(js, "dotenv_expand");
}
#[test]
fn fp_process_env_usage() {
let js = r#"
const NODE_ENV = process.env.NODE_ENV || 'development';
if (NODE_ENV === 'development') {
console.log('Running in development mode');
}
const dbPass = process.env.DB_PASS;
connectToDatabase(dbPass);
function connectToDatabase(pass) {
console.log('Connecting...');
}
"#;
must_be_clean(js, "process_env_usage");
}