Open kongmingLatern opened 1 year ago
要求:
给每个 console 前面插入文件名以及行列号,方便之后定位到指定代码
1.1 整体思路
![[Pasted image 20230114122408.png]]
函数调用表达式的 AST 是 CallExpression。
CallExrpession 节点有两个属性,callee 和 arguments,分别对应调用的函数名和参数, 所以我们要判断当 callee 是 console.xx 时,在 arguments 的数组中中插入一个 AST 节点。
![[Pasted image 20230114122842.png]] 1.2 整体框架
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const sourceCode = `console.log(1);`;
const ast = parser.parse(sourceCode, {
sourceType: 'unambiguous'
});
traverse(ast, {
CallExpression(path, state) {
}
});
const { code, map } = generate(ast);
console.log(code);
要转化的代码
const sourceCode = `
console.log(1);
function func() {
console.info(2);
}
export default class Clazz {
say() {
console.debug(3);
}
render() {
return <div>{console.error(4)}</div>
}
}
`;
修改结构
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const ast = parser.parse(sourceCode, {
sourceType: 'unambiguous',
plugins: ['jsx']
});
traverse(ast, {
CallExpression (path, state) {
if ( types.isMemberExpression(path.node.callee)
&& path.node.callee.object.name === 'console'
&& ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
) {
const { line, column } = path.node.loc.start;
path.node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
}
}
});
修改上述代码实现
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');
const ast = parser.parse(sourceCode, {
// sourceType 用来告诉编辑器是使用 mjs 还是 cjs
// unambiguous 让编译器自行推断
sourceType: 'unambiguous',
plugins: ['jsx']
});
const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
traverse(ast, {
// 函数调用表达式的 AST 是 CallExpression。
CallExpression(path, state) {
const calleeName = generate(path.node.callee).code;
if (targetCalleeName.includes(calleeName)) {
const { line, column } = path.node.loc.start;
path.node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
}
}
});
最终代码:
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const types = require('@babel/types');
const template = require('@babel/template').default;
const ast = parser.parse(sourceCode, {
sourceType: 'unambiguous',
plugins: ['jsx']
});
const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
traverse(ast, {
CallExpression(path, state) {
if (path.node.isNew) {
return;
}
const calleeName = generate(path.node.callee).code;
if (targetCalleeName.includes(calleeName)) {
const { line, column } = path.node.loc.start;
const newNode = template.expression(`console.log("filename: (${line}, ${column})")`)();
newNode.isNew = true;
if (path.findParent(path => path.isJSXElement())) {
path.replaceWith(types.arrayExpression([newNode, path.node]))
path.skip();
} else {
path.insertBefore(newNode);
}
}
}
});
API 参考:
babelParser.parse(code, [options])
babelParser.traverse(parent, [options])
parent 指定要遍历的 AST 节点, opts 指定 visitor 函数。 babel 会在遍历 parent 对应的 AST 时调用相应的 visitor 函数。
function generate(ast, opts, code: string): {code, map}
第一个参数是要打印的 AST。 第二个参数是 options,指定打印的一些细节,比如通过 comments 指定是否包含注释,通过 minified 指定是否包含空白字符。 第三个参数当多个文件合并打印的时候需要用到,这部分直接看文档即可,基本用不到。 options 中常用的是 sourceMaps,开启了这个选项才会生成 sourcemap。
![[Pasted image 20230114191402.png]]
impl Solution {
// 注意:这里的数组需要 mut 关键字,因为 sort 方法会破坏本身的数组
pub fn get_least_numbers(mut arr: Vec<i32>, k: i32) -> Vec<i32> {
arr.sort();
// 数组切片,并把结果变成一个数组
arr[..(k as usize)].to_vec()
}
}
补充:
let a = [1, 2, 3, 4, 5];
// 切片
let slice = &a[1..3];
// 该数组切片的类型是 &[i32]
assert_eq!(slice, &[2, 3]);
use std::cmp::Reverse;
let mut v = vec![1, 2, 3, 4, 5, 6];
v.sort_by_key(|&num| (num > 3, Reverse(num)));
assert_eq!(v, vec![3, 2, 1, 6, 5, 4]);
use std::collections::BinaryHeap;
// Type inference lets us omit an explicit type signature (which
// would be `BinaryHeap<i32>` in this example).
let mut heap = BinaryHeap::new();
// We can use peek to look at the next item in the heap. In this case,
// there's no items in there yet so we get None.
assert_eq!(heap.peek(), None);
// Let's add some scores...
heap.push(1);
heap.push(5);
heap.push(2);
// Now peek shows the most important item in the heap.
assert_eq!(heap.peek(), Some(&5));
// We can check the length of a heap.
assert_eq!(heap.len(), 3);
// We can iterate over the items in the heap, although they are returned in
// a random order.
for x in &heap {
println!("{x}");
}
// If we instead pop these scores, they should come back in order.
assert_eq!(heap.pop(), Some(5));
assert_eq!(heap.pop(), Some(2));
assert_eq!(heap.pop(), Some(1));
assert_eq!(heap.pop(), None);
// We can clear the heap of any remaining items.
heap.clear();
// The heap should now be empty.
assert!(heap.is_empty())
type ToPrimitive<T extends Record<string, any>> = {
[P in keyof T] :
T[P] extends number
? number
: T[P] extends string
? string
: T[P] extends boolean
? boolean
: T[P] extends Record<string ,any>
? ToPrimitive<T[P]>
: never
// ====================Test Cases====================//
type PersonInfo = {
name: 'Tom'
age: 30
married: false
addr: {
home: '123456'
phone: '13111111111'
}
hobbies: ['sing', 'dance']
}
type ExpectedResult = {
name: string
age: number
married: boolean
addr: {
home: string
phone: string
}
hobbies: [string, string]
}
2023-01-14
1. 你学习了哪些知识?
2. 学习过程中是否有存在的问题?
关于如何写每日任务:
如何写每日任务