eslint plugin 开发笔记

新建项目

1
2
3
4
npm install -g yo generator-eslint
mkdir eslint-plugin-demo
cd eslint-plugin-demo
yo eslint:plugin

目录结构如下:

1
2
3
4
5
6
7
8
9
.
├── README.md
├── lib
│   ├── index.js
│   └── rules
├── package.json
└── tests
└── lib
└── rules

接着执行

1
yo eslint:rule

此时目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
├── README.md
├── docs
│   └── rules
│   └── no-console-time.md
├── lib
│   ├── index.js
│   └── rules
│   └── no-console-time.js
├── package.json
└── tests
└── lib
└── rules
└── no-console-time.js

在 eslint-plugin-demo 目录下执行

1
npm link

控制台会输出

1
/usr/local/lib/node_modules/eslint-plugin-demo -> /Users/your-name/workspace/eslint/eslint-plugin-demo

然后在需要使用该 npm 包的项目中执行

1
npm link eslint-plugin-demo

控制台会输出

1
/Users/your-name/workspace/eslint/eslint-example/node_modules/eslint-plugin-demo -> /usr/local/lib/node_modules/eslint-plugin-demo -> /Users/daomeng.pan/workspace/eslint/eslint-plugin-demo

如果需要解除 link ,则直接删除项目中 node_modules 下的软链接即可

1
rm /Users/your-name/workspace/eslint/eslint-example/node_modules/eslint-plugin-demo

编写 eslint rule

yo eslint:rule 生成的模板文件如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @fileoverview this is a description
* @author pandaomeng
*/
'use strict';

module.exports = {
meta: {
docs: {
description: 'this is a description',
category: 'Fill me in',
recommended: false,
},
fixable: null, // or "code" or "whitespace"
schema: [
// fill in your schema
],
},

create: function (context) {
return {
// give me methods
};
},
};
  • meta

    • docs
      • description: rule 的描述
      • category: rule 所属的类目,参考 eslint 官方提供的 rule,有:’Best Practices’、’Stylistic Issues’、’Possible Errors’、 ‘Variables’ 、’Strict Mode’、’ECMAScript 6’ 、’Deprecated’ 、 ‘Removed’等,详见 https://eslint.org/docs/rules/
      • recommended: 当 eslint 配置中开启 “extends”: “eslint:recommended” 时,是否启用这条规则。
    • schema: 使用 Json schema 语法描述 eslint 可接收的 options 的结构
    • fixable: 在使用 --fix 的时候是否自动修复
  • create: (function) 返回一个对象,其中包含了 ESLint 在遍历 JavaScript 代码的抽象语法树 AST (ESTree 定义的 AST) 时,用来访问节点的方法。

    • 如果一个 key 是个节点类型或 selector,在 向下 遍历树时,ESLint 调用 visitor 函数

    • 如果一个 key 是个节点类型或 selector,并带有 :exit,在 向上 遍历树时,ESLint 调用 visitor 函数

    • 如果一个 key 是个事件名字,ESLint 为代码路径分析调用 handler 函数

create 方法举例

我们可以借助 ast语法树分析网站,在里面得到我们的语法树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
// 省略...
create: function (context) {
return {
Identifier: (node) => {
const identifier = node.name
// 可以对 identifier 做一些操作和判断,例如:
if (identifier === 'foo') {
context.report({
node: node.parent,
message: '这是一个自定义报错: 不可以使用 {{name}} 作为标注符',
data: {
name: identifier
}
});
}
},
};
},
};

对 import 语句的校验

使用 eslint-module-utils 提供的方法可以提高开发效率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { default: moduleVisitor } = require('eslint-module-utils/moduleVisitor');

module.exports = {
// 省略...
create(context) {
const checkSourceValue = (source, importer) => {
// 具体的校验逻辑, source 为引入的路径,importer 为整个 import 语法节点
};
return {
...moduleVisitor(checkSourceValue, context.options[0]),
}
},
};

参考源码 https://github.com/benmosher/eslint-plugin-import/blob/master/utils/moduleVisitor.js

context 对象

create 函数接收一个 context 对象

提供了很多常用的方法和属性,包括 getSourceCode() 获取源码、getFileName() 获取文件名、context.options 获取配置的参数(schema 中约定好的结构) 等

正畸正颌记

​ 一直觉得自己的下巴比别人的要长,对自己的长相也一直挺自卑的。直到上大学的时候才真正知道,自己的这种症状是因为牙齿咬合不正畸形导致的,我属于比较严重的地包天加偏颌。看了很多网上牙友的看牙经历,知道了正畸正颌这一神奇的治疗方法,也看到了很多治疗成功的案例,我决定等毕业攒够钱一定要去做这个治疗。

​ 2019.06.17 我来到了上海的一家公司,这家公司距离上海九院 5公里。(上海九院是全国正畸正颌做得最好的几家医院之一),但是因为经济的原因,一直没开始我的治疗。2020 年 11 月初,我在网上挂了个九院 ”正畸正颌“ 普通初诊号。

2020.11.14 第一次挂号面诊(周六)

​ 到了初诊的这一天,我 9:00 来到九院,先是尝试在自助挂号机上挂号,但是”正畸正颌“是无法使用医保卡的,自然是挂号失败了。然后只能去窗口排队取就诊卡。九院窗口挂号的队伍很长,排了半小时才排到。挂完号之后就直接去四楼的正畸正颌科等着了。等了不是很久就到我了,面诊我的是8诊台的丁医生,她看了看我的情况,就跟我说了我这种情况是需要做正畸正颌联合治疗的,并且跟我说了治疗时长可能要两三年,费用需要十几万。因为我事先做好调研了,所以我表示都没有问题,接着丁医生就给我开了张单子,开始拍片了。在六楼交完 2800元的拍片费用之后,我当天拍完了牙片、全景、CT,核磁共震约在了下周六 11.21。还做了一项抽血,抽血是为了验是否有乙肝。把 CT 的牙片给了丁医生后,约了下次来取牙模的时间。差不多到中午的时候就全部结束回家了。

​ 进展比我预想的要快很多,本来预期只是来稍微了解下情况,没想到就直接交完钱拍好片了。

2020.11.21 拍核磁共振(周六)

​ 今天是专门来拍核磁共振(MRI)的,顺便取了下之前做的 CT胶片和验血单子。进入核磁共振巨大的铅门,医生让我躺在 MRI 的仪器上,带上耳罩,开始 MRI。其中一段过程要求我咬住一个塑料块。整个过程在头部周围一直有各种频率刺耳的声音。差不多几分钟后就结束了。中午之前就结束回家了。

2020.11.24 拍片&口扫(周二)

今天来口扫了,首先是一个医生拿着扫描器对着你的口腔一顿扫描,估计是为了出 3D 打印模型。然后去拍照,照相师把我的嘴撑开,对着牙的各个角度都拍了照片。最后就是去了一个牙模室,一个护士拿石膏一样的东西盖在你的上牙齿2分钟,等凝固后去下,然后是下牙。接着就跟丁医生约了会诊的时间。差不多也是中午前就结束了。

2020.12.8 会诊 & 拔牙(周二)

​ 会诊就是正畸医生和动手术的正颌医生一起讨论出一个正畸方案,我从早上 8:30 等到了 11点,终于轮到我的会诊了,全程就是丁医生在讲大概的正畸方案,然后正颌医生点了点头,全程两分钟左右就结束了。最后定下来的拔牙方案是,把上颌的两颗4号牙外加3颗智齿,因为有一颗智齿之前拔掉了。智齿不影响正畸带牙套,只要拔掉两个四号牙就可以开始正畸了。本来想的拔牙怎么说也要一个月时间吧,就和丁医生约了 12.31 号上牙套。

​ 下午的时候在颅颌面科挂了个号,排到之后进入诊室,医生开了一张单子让我去一楼拿消炎药和两针麻醉剂。拔牙的过程很快,5分钟不到,两颗4号牙就都拔完了。拔牙比预想的快了很多,就在微信上跟丁医生提前了带牙套的时间,约在了 12.17 号。拔完牙咬了纱布 45 分钟后吐掉了,嘴里还有一点点血,不过过了两小时左右,喝了点水,基本就没有血了。

​ 回家的时候带了个石膏牙模回去。

2020.12.17 上牙套(周四)

​ 今天预约的是早上 10点钟,随意我比平时多睡了会儿。到九院 4楼正畸科的时候刚好 9.50,看到叫号机上写着我的名字,我就直接进去了。丁医生在专家四诊台等着我,我到了以后,丁医生去取了下工具,我躺下来就开始粘牙套的托槽了。正畸医生先往我的牙齿上喷了一些酸酸的东西,酸蚀牙齿,然后一个个托槽往上粘,粘完托槽以后,医生用铁丝将所有托槽连在一起。过程粘了两个小时。

​ 中午回家的时候在白玉兰吃了碗牛肉粒手擀面,右下颌最里面的托槽直接就崩掉了,联系了丁医生,下午又回到九院重新粘上。

​ 晚上和公司同事聚餐,感觉到牙上下牙咬合的时候很疼,不能咬东西,将就吃了点东西。

​ 每顿饭后牙套里都塞满了食物,都需要认真刷牙。买了一款漱口水,刷完牙含漱一会儿。

git 加速

在本地拥有代理的情况下,配置命令行 git 加速

一、配置 .ssh/config 文件

配置 http 代理监听 127.0.0.1:1087

1
vim ~/.ssh/config

添加文件内容如下:

1
2
3
Host github.com
User git
ProxyCommand nc -x localhost:1086 %h %p

即可走 http 代理,效果如下

二、配置 git 走 socks5

配置 socks5 代理监听 127.0.0.1:1086

在命令行中执行以下命令

1
2
git config --global http.proxy "socks5h://localhost:1086"
git config --global https.proxy "socks5h://localhost:1086"

即可。

前端面试题整理

call、apply、bind

手动实现 call

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myCall = function (context, ...args) {
context = context || window
let symbol = Symbol()
context[symbol] = this
context[symbol](...args)
delete context[symbol]
}

function sayHi () {
console.log(`Hi, ${this.name}`)
}
var a = {name: 'Patrick'}
sayHi.myCall(a)
  • Symbol() 处可以使用一个空对象代替,因为对象的引用地址一定是唯一的。

手动实现 apply

同 call 的实现,只有接收参数的地方不同。

1
2
3
Function.prototype.myApply = function (context, args) {
......
}

手动实现 bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var myBind = function (context, ...presetArgs) {
return (...args) => {
this.call(context, ...presetArgs, ...args)
}
}

Function.prototype.myBind = myBind

function foo(arg1, arg2) {
console.log(this, arg1, arg2)
}

// case 1
foo('hello', 88)

// case 2
var a = {}
foo.myBind(a, 'world')(99)

使用 babel 转义为 es5 代码

  • 注意第二行返回的是一个箭头函数,这边的 this 才是 foo,否则的话 this 会指向 window。
  • bind 可以传预设的参数列表,可以用作函数 curly 化。
  • 连续的 bind 只有第一个 bind 会生效,解释如下:
    • es6 的代码中,使用的是箭头函数,this 指向在调用第一个 bind 的时候就确定了。
    • es5 的代码中,函数调用的时候,后面的 bind 返回的函数反而会先执行,最后实际起作用的是第一个 bind 绑定的对象。

TODO

[-] 如何实现连续 bind,this 指向最后一个绑定的对象?

=> 箭头函数的实现

继承相关

  1. 有几种继承方式

Class

Class 转成 es5 之后的代码

Reflect

判断数据的类型

可以通过 typeof 或者 instanceof 来判断数据的类型

typeof 只能判断基础数据类型,instanceof 可以用来判断的对象的类型

A instanceof B 判断 B 是否存在于 A 的原型链上

如何通过 instanceof 来判断一个基础数据类型?

答:创建一个类 ,改写这个 function 的 [Symbol.hasInstance] 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PrimitiveString {
static [Symbol.hasInstance] = (x) => {
return typeof x === 'string'
}
}

// 用 es5 实现
function PrimitiveString() {}
Object.defineProperty(PrimitiveString, Symbol.hasInstance, {
value: function (x) {
return typeof x === 'string';
}
});

// 错误的写法,错误的原因:通过 Assignment 的方式去改写 [Symbol.xxx] 属性不会生效。
function PrimitiveString() {}
PrimitiveString[Symbol.hasInstance] = function (x) {
return typeof x === 'string';
}

清除无效的分支

清除本地中无效的远程追踪分支

通过下列的命令可以删除 本地中无效的远程追踪分支

即:远端已经删除过了,本地还存在 remotes/origin/fix/foo,则可通过这个命令清理

1
git remote prune origin

该命令不会删除本地的分支

清除本地的分支

通过以下命令我们可以找到所有带有 fix 或者 feat 的分支

1
git branch | grep -E 'fix|feat'

如果我们想删除这些分支,只需执行以下命令

1
git branch | grep -E 'fix|feat' | xargs git branch -D

如果你只想保留本地中某些分支,我们可以通过 grep -v 反选的功能来查找

1
git branch | grep -vE 'develop|module|release'

同理,我们再添加删除分支的管道 xargs git branch -D

1
git branch | grep -vE 'develop|module|release' | xargs git branch -D

执行完以上命令,我们本地就得到了只包含有 ‘develop|module|release’ 的分支了

千万注意,如果要执行删除分支的命令,一定要确保不要到删除自己正在开发的分支!

hexo 中插入数学公式

前期准备

使用 hexo-renderer-pandoc 渲染的前提条件是,当前的操作系统安装过 pandoc

安装教程:https://github.com/jgm/pandoc/blob/master/INSTALL.md#linux

以 Linux 举例,从 release 下载最新的包并安装

1
2
wget https://github.com/jgm/pandoc/releases/download/2.9.2.1/pandoc-2.9.2.1-1-amd64.deb
sudo dpkg -i pandoc-2.9.2.1-1-amd64.deb

配置

  1. 替换 hexo 的渲染引擎,在 hexo 的项目根目录执行以下命令

    1
    2
    npm uninstall hexo-renderer-marked --save
    npm install hexo-renderer-pandoc --save
  2. 在主题文件夹的目录下,编辑 _config 文件,打开 MathJax 的开关

    1
    2
    3
    4
    5
    # MathJax Support
    mathjax:
    enable: true
    per_page: true
    cdn: //cdn.bootcss.com/mathjax/2.7.1/latest.js?config=TeX-AMS-MML_HTMLorMML
  3. 在写博客的时候,在文章的 Front-matter 处声明开启 MathJax

    1
    2
    3
    4
    title: 切披萨的方案数题解
    date: 2020-05-14
    tag: [leetcode, dp]
    mathjax: true
  4. 配置完成后执行重新生成,之前写好的公式就能正常显示了

    1
    2
    hexo clean
    hexo generate

相关工具

typora

Markdown 编辑器,公式会被实时转义。效果如下图

切披萨方案数题解

原题

Leetcode 切披萨方案数

查看原题

给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: ‘A’ (表示苹果)和 ‘.’ (表示空白格子)。你需要切披萨 k-1 次,得到 k 块披萨并送给别人。

切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。

请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。

示例 1:

1
2
3
输入:pizza = ["A..","AAA","..."], k = 3
输出:3
解释:上图展示了三种切披萨的方案。注意每一块披萨都至少包含一个苹果。

示例 2:

1
2
输入:pizza = ["A..","AA.","..."], k = 3
输出:1

示例 3:

1
2
输入:pizza = ["A..","A..","..."], k = 1
输出:1

提示:

1
2
3
4
5
1 <= rows, cols <= 50
rows == pizza.length
cols == pizza[i].length
1 <= k <= 10
pizza 只包含字符 'A' 和 '.' 。

分析

前置说明

为了描述方便,我们用 披萨(i, j) 表示左上角坐标为 (i, j) 右下角坐标为(rows -1 ,cols-1) 的披萨

使用 dp 变量表示状态

dp[i][j][k] 表示 披萨(i, j) 切 k 次 分给 k+1个人 一共有多少种情况

拿例一中的图举例:

dp[2][2][0] == 0 表示下图的红框中的披萨切0次,分给1个人,共有 0 种情况。

dp[1][1][1] == 1 表示下图红框中的披萨切1次,分给 2个人,共有1情况

题目中所求的即为 dp[0][0][k-1] 的值,他代表的意思为,从 披萨(0, 0)中切 k-1 次分给 k 个人的情况数

状态转移方程

dp 算法的关键是,如何将一个问题转化为子问题进行求解,即写出状态转移方程。

公式

$$dp[i][j][k] = \sum_{r=i+1}^{rows-1}(hasApple(i,j,r,j) ? dp[r][j][k-1] : 0) + \sum_{r=j+1}^{cols -1}(hasApple(i,j,i,r) ? dp[i][r][k-1] : 0)$$

其中:

$$hasApple(i,j,i,j) = remains[i]][j] - reamains[i][j] > 0$$

解释

dp[i][j][k]

对于任意的 披萨 (i, j),我们遍历他们所有的切割方法(包括横切和纵切),记每种切割完后剩余的披萨为 (i', j'),则剩下的披萨的切割情况数可以用dp[i'][j'][k-1]来表示,我们将所有的这些满足条件的子情况数加起来,就得到了dp[i][j][k]

hasApple

代表切下来的部分是否含有苹果。

remains[i][j] 数组表示披萨 (i, j) 包含的苹果总数

记切除后的披萨为 披萨(i', j')

则切下来的部分的披萨包含苹果数就可以用 remains[i][j] - remains[i'][j'] 来表示了

如何求 remains 数组

我们观察上图,remains[x][y] 的值就等于 他本身 + 绿色的框 + 蓝色的框 - 红色的框(重复叠加了)

即 $remains[x][y] = current + remains[x+1][y] + remains[x][y+1] - remains[x+1][y+1] $

通过上述dp转移方程,我们算出披萨中的每个点的remains值存在数组中备用。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
var ways = function (pizza, k) {
let row = pizza.length;
let column = pizza[0].length;
let remains = new Array(row).fill(null).map((each) => []);
let mod = 1e9 + 7;

for (let i = row - 1; i >= 0; i--) {
for (let j = column - 1; j >= 0; j--) {
let current = pizza[i][j] === 'A' ? 1 : 0;
if (i === row - 1) {
remains[i][j] = (remains[i][j + 1] || 0) + current;
continue;
}
if (j === column - 1) {
remains[i][j] = (remains[i + 1][j] || 0) + current;
continue;
}

remains[i][j] =
remains[i][j + 1] + remains[i + 1][j] + current - remains[i + 1][j + 1];
}
}

let dp = [];
for (let i = 0; i < row; i++) {
dp[i] = new Array();
for (let j = 0; j < column; j++) {
dp[i][j] = new Array(k).fill(0);
}
}

for (let i = row - 1; i >= 0; i--) {
for (let j = column - 1; j >= 0; j--) {
if (remains[i][j] > 0) dp[i][j][0] = 1;

for (let cut = 1; cut < k; cut++) {
// 横着切
for (let r = i + 1; r < row; r++) {
if (remains[i][j] - remains[r][j] > 0) {
dp[i][j][cut] = (dp[r][j][cut - 1] + dp[i][j][cut]) % mod;
}
}
// 竖着切
for (let r = j + 1; r < column; r++) {
if (remains[i][j] - remains[i][r] > 0) {
dp[i][j][cut] = (dp[i][r][cut - 1] + dp[i][j][cut]) % mod;
}
}
}
}
}
return dp[0][0][k - 1];
};

使用 Let’s Encrypt 注册 ssl 证书

本文使用环境

ubuntu 16.04

Let’s Encrypt 简介

为了在您的网站上启用 HTTPS,您需要从证书颁发机构(CA)获取证书(一种文件)。Let’s Encrypt 是一个证书颁发机构(CA)。要从 Let’s Encrypt 获取您网站域名的证书,您必须证明您对域名的实际控制权。您可以在您的 Web 主机上运行使用 ACME 协议的软件来获取 Let’s Encrypt 证书。– 摘自 letsencrypt.org

Cerbot 简介

Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS.

Certbot is made by the Electronic Frontier Foundation (EFF), a 501(c)3 nonprofit based in San Francisco, CA, that defends digital privacy, free speech, and innovation. – 摘自 cerbot 官网 certbot.eff.org

Certbot 是Let’s Encrypt官方推荐的获取证书的客户端,可以帮我们获取免费的Let’s Encrypt 证书。

安装 cerbot

1
2
3
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-apache

申请证书 & 配置

1
certbot certonly --webroot -d pandaomeng.com -d www.pandaomeng.com

证书生成完毕后,我们可以在 /etc/letsencrypt/live/ 目录下看到对应域名的文件夹,里面存放了指向证书的一些快捷方式。

这时候我们的第一生成证书已经完成了,接下来就是配置我们的web服务器,启用HTTPS。

配置 web 服务器,启用 nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
listen 80;
listen [::]:80;

rewrite ^(.*)$ https://$host$1 permanent;

server_name pandaomeng.com www.pandaomeng.com;
}

server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/pandaomeng.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pandaomeng.com/privkey.pem;
root /var/www/html;

index index.html index.htm index.nginx-debian.html;

server_name pandaomeng.com www.pandaomeng.com;

if ($host != 'www.pandaomeng.com' ) {
rewrite ^/(.*)$ https://www.pandaomeng.com/$1 permanent;
}

location / {
try_files $uri $uri/ =404;
}
}

自动更新 SSL 证书

Let’s Encrypt 证书 90天过期,我们使用 linux 系统自带的 cron 来配置定时任务,在证书即将到期的时候运行命令自动更新证书

执行

1
crontab -e

添加以下内容

1
* */12 * * * certbot renew --quiet --renew-hook "/etc/init.d/nginx reload"

它表示,每 12 小时,系统会执行一次 certbot renew 的命令,执行成功之后重启 nginx

至此,免费 SSL 证书配置完成。

其他

如果使用七牛的图床,可以使用七牛云的免费证书

npm 相关命令记录

查看全局安装过的包

1
npm list -g --depth 0

参数解释

命令 解释
npm list 显示安装过的包
-g 指全局安装过的包
–depth 0 限制输出模块层级

从coding转移git仓库到github

步骤

  1. 获取源git仓库的地址

    如: git@git.coding.net:pandameng/xxx.git

  2. 在github上创建一个空的仓库

    如:git@github.com:pandaomeng/xxx.git

  3. 随意找个目录,执行以下命令

    1
    2
    3
    git clone --bare git@git.coding.net:pandameng/xxx.git # 源仓库地址
    cd xxx.git/
    git push --mirror git@github.com:pandaomeng/xxx.git # 目标地址
0%