├── .vscode └── lua.booster.lint.json ├── test └── 101.lua ├── README.md ├── src └── lints │ ├── basic │ ├── 106_line_size.js │ ├── 137_utf8.js │ ├── 108_global_variable.js │ ├── 109_line_length.js │ ├── 135_upper_case_global.js │ ├── 125_remove_end_spaces.js │ ├── 104_duplicate_args.js │ ├── 133_no_single_name.js │ ├── 123_space_near_comma.js │ ├── 105_duplicate_keys.js │ ├── 126_no_comma_starting_a_line.js │ ├── 121_space_around_assign.js │ ├── 114_not_too_many_circuits.js │ ├── 107_unresolve_reference.js │ ├── 122_space_around_BOP.js │ └── 136_comments.js │ ├── intermediate │ ├── 124_blank_line_at_the_end.js │ ├── 102_table_function.js │ ├── 131_judge_directly.js │ ├── 112_try_no_define_func.js │ ├── 113_no_arg_as_param.js │ ├── 103_no_this_arg.js │ ├── 101_table_property.js │ ├── 111_function_brackets.js │ └── 134_for_in_underline.js │ └── advance │ ├── 143_judge_empty_table.js │ ├── 127_no_semicolon_in_line.js │ ├── 132_type_cast.js │ ├── 142_table_index_from_zero.js │ ├── 141_for_string_concat.js │ ├── 144_no_sharp_inFrontOf_hash.js │ └── 145_use_ipairs_for_array.js ├── package.json ├── LICENSE ├── .gitignore └── doc └── standard.html /.vscode/lua.booster.lint.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "advance": true, 4 | "basic": true, 5 | "intermediate": true, 6 | "lua-check": true 7 | }, 8 | "rules": {} 9 | } -------------------------------------------------------------------------------- /test/101.lua: -------------------------------------------------------------------------------- 1 | --Try define a table`s property while creating the table, not outside it. 2 | -- bad 3 | local user = {} 4 | user.name = "Robin" 5 | user.level = 56 6 | user.grade = "gold" 7 | 8 | -- good 9 | local user = { 10 | name = "Robin", 11 | level = 56, 12 | grade = "gold" 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vscode-lua-booster 2 | Boost your productivity and happiness when coding with Lua. 3 | 4 | # How to import your lints 5 | - you should write unit tests & examples for your lints 6 | - `ctrl+shift+p` and type `lua booster open lint` (you just need to type part or CMD, like `booster lint`) 7 | - open the lint folder and put your lint files here 8 | 9 | # roadmap 10 | - test framework for lints 11 | -------------------------------------------------------------------------------- /src/lints/basic/106_line_size.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "line_size", 3 | tags: ['basic'], 4 | desc: { cn: '超出最大行数(500)。', en: 'The document has too many lines.(more than 500).' }, 5 | document: 106, 6 | check(document) { 7 | let lineCount = document.getLineSize(); 8 | if (lineCount > 500) { 9 | return [{ 10 | range: document.getRange(), 11 | severity: 2 12 | }]; 13 | } 14 | return []; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/lints/basic/137_utf8.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "utf8", 3 | tags: ['basic'], 4 | desc: { cn: '代码文件必须为utf8编码。', en: 'The coding file must be encoded by utf8.' }, 5 | document: 137, 6 | check(document) { 7 | let firstLineRange = document.getLineRangeEx(0); 8 | if (document.encoding !== 'UTF-8' && document.encoding !== 'ascii') { 9 | return [{ 10 | range: firstLineRange, 11 | severity: 1 12 | }]; 13 | } 14 | return []; 15 | } 16 | }; 17 | //2.18 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-lua-booster", 3 | "version": "1.0.0", 4 | "description": "Boost your productivity and happiness when coding with Lua.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/operali/vscode-lua-booster.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/operali/vscode-lua-booster/issues" 18 | }, 19 | "homepage": "https://github.com/operali/vscode-lua-booster#readme" 20 | } 21 | -------------------------------------------------------------------------------- /src/lints/basic/108_global_variable.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "global variable", 3 | tags: ['basic'], 4 | desc: { cn: '尽量不要暴露全局变量。', en: 'Try not to expose global variable as possible' }, 5 | document: 108, 6 | check(document) { 7 | let diagnostics = []; 8 | let globals = document.getGlobalSymbols(); 9 | // 最多显示5个 10 | for (let i = 0; i < globals.length && i < 5; ++i) { 11 | let sym = globals[i]; 12 | 13 | if (sym.getName() === '_G') { 14 | continue; 15 | } 16 | diagnostics.push({ range: sym.getSelectRange(), severity: 2 }); 17 | } 18 | 19 | return diagnostics; 20 | } 21 | }; 22 | 23 | //2.6.1 -------------------------------------------------------------------------------- /src/lints/intermediate/124_blank_line_at_the_end.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'Blank_line_at_the_end', 4 | desc: { cn: '用一空行作为文件结尾.', 5 | en: 'Add a blank line at the end of a file.' }, 6 | tags: ['intermediate'], 7 | document: 124, 8 | check: function (document) { 9 | let totalLine = document.getLineSize(); 10 | let lineRange = document.getLineRangeEx(totalLine-1); 11 | let text = document.getTextEx(lineRange.start, lineRange.end); 12 | if (!text.match(/^[ ]*$/) && text !== '\r') { 13 | return [{ 14 | range: lineRange, 15 | severity: 3 16 | }]; 17 | } 18 | return []; 19 | } 20 | }; 21 | return rule; 22 | //2.8.5 -------------------------------------------------------------------------------- /src/lints/basic/109_line_length.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "line_length", 3 | tags: ['basic'], 4 | desc: { cn: '一行不能超出120个字符。', en: 'Do not put over 120 characters in a line.' }, 5 | document: 109, 6 | check(document) { 7 | let lineCount = document.getLineSize(); 8 | for (let i = 0; i < lineCount; ++i) { 9 | let fromTo = document.getLineRange(i); 10 | if (fromTo.to - fromTo.from > 120) { 11 | let start = document.positionAt(fromTo.from); 12 | let end = document.positionAt(fromTo.to); 13 | return [{ 14 | range: { start, end }, 15 | severity: 2 16 | }]; 17 | } 18 | } 19 | return []; 20 | } 21 | }; 22 | 23 | //2.7.1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 operali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lints/advance/143_judge_empty_table.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'judge_empty_table', 4 | desc: { cn: '使用next判断空表。', 5 | en: 'Use next to judge whether a table is empty.' }, 6 | tags: ['advance'], 7 | document: 143, 8 | check: function (document) { 9 | let nodes = document.ast.select(function (node) { 10 | let BOP = node.anyNode().asBOP(); 11 | if (!BOP) { 12 | return false; 13 | } 14 | let left = BOP.getLeft(); 15 | let rights = BOP.getRights(); 16 | let leftType = left.getType(); 17 | let rightType = rights[0].exp.getType(); 18 | if (leftType == 'table' && rightType == 'nil') { 19 | return true; 20 | } 21 | if (leftType == 'nil' && rightType == 'table') { 22 | return true; 23 | } 24 | return false; 25 | }, 1); 26 | if (nodes.length > 0) { 27 | return [{ 28 | range: nodes[0].getRange(), 29 | severity: 3 30 | }]; 31 | } 32 | return []; 33 | } 34 | }; 35 | return rule; 36 | //3.2.3 -------------------------------------------------------------------------------- /src/lints/intermediate/102_table_function.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'table_function', 4 | desc: { cn: '在表定义的外部定义函数', 5 | en: "It's recommended to define a table's function outside a table, not inside it." }, 6 | tags: ['intermediate'], 7 | document: 102, 8 | check: function (document) { 9 | let retRange; 10 | let nodes=document.ast.select(node=> { 11 | if(!node.isTable){ 12 | return false; 13 | } 14 | let tab=node.anyNode().asTable(); 15 | if(!tab){ 16 | return false; 17 | } 18 | let fields=tab.getFields(); 19 | for(let field of fields){ 20 | let val=field.getValue(); 21 | if(val && val.asFunctionBody()){ 22 | retRange = field.getRange(); 23 | return true; 24 | } 25 | } 26 | return false; 27 | },1); 28 | 29 | if(nodes.length>0){ 30 | return [{ 31 | range: retRange, 32 | severity: 3 33 | }] 34 | } 35 | return []; 36 | } 37 | }; 38 | return rule; 39 | //2.3.2 -------------------------------------------------------------------------------- /src/lints/intermediate/131_judge_directly.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'judge_directly', 4 | desc: { cn: '除非需要明确false与nil,否则直接利用条件判断即可。', 5 | en: 'Judge true directly unless you have to specify false and nil' }, 6 | tags: ['intermediate'], 7 | document: 131, 8 | check: function (document) { 9 | var nodes = document.ast.select(function (node) { 10 | var BOP = node.anyNode().asBOP(); 11 | if (!BOP) { 12 | return false; 13 | } 14 | var rights = BOP.getRights(); 15 | for (var _i = 0, rights_1 = rights; _i < rights_1.length; _i++) { 16 | var right = rights_1[_i]; 17 | var op = right.op; 18 | if (op == "~=" && (right.exp.getText() == "false" || right.exp.getText() == "nil")) { 19 | return true; 20 | } 21 | } 22 | return false; 23 | }, 1); 24 | if (nodes.length > 0) { 25 | return [{ 26 | range: nodes[0].getRange(), 27 | severity: 3 28 | }]; 29 | } 30 | return []; 31 | } 32 | }; 33 | return rule; 34 | //2.11.2 -------------------------------------------------------------------------------- /src/lints/intermediate/112_try_no_define_func.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'try no define func', 4 | desc: { cn: ' 尽量不要用变量定义的方式定义函数,以便对匿名函数进行区分', 5 | en: 'Try not to define a function using a definition expression.' }, 6 | tags: ['intermediate'], 7 | document: 112, 8 | check: function (document) { 9 | let retRange; 10 | let nodes=document.ast.select(node=>{ 11 | let assignNode = node.anyNode().asAssign(); 12 | if(!assignNode){ 13 | return false; 14 | } 15 | let assigns = assignNode.getAssigns(); 16 | for(let ass of assigns){ 17 | if(!ass.exp){ 18 | return false; 19 | } 20 | if(ass.exp.anyNode().asFunctionBody()){ 21 | retRange = ass.exp.getRange(); 22 | return true; 23 | } 24 | } 25 | return false; 26 | },1); 27 | 28 | if(retRange){ 29 | return [{ 30 | range: retRange, 31 | severity: 3 32 | }]; 33 | } 34 | return []; 35 | } 36 | }; 37 | return rule; 38 | //2.5.3 -------------------------------------------------------------------------------- /src/lints/basic/135_upper_case_global.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'global_variable_upper_case', 4 | desc: { cn: '全局的常量命名,均用大写,区分普通变量', 5 | en: 'The name of a global variable should all be in upper case.' }, 6 | tags: ['basic'], 7 | document: 135, 8 | check: function (document) { 9 | var globals = document.getGlobalSymbols(); 10 | for (var i = 0; i < globals.length; ++i) { 11 | var sym = globals[i]; 12 | if (sym.getName() === '_G') { 13 | continue; 14 | } 15 | var range = sym.getSelectRange(); 16 | var name_1 = sym.getName(); 17 | // let startNum = document.offsetAt(range.start); 18 | // let endNum = document.offsetAt(range.end); 19 | var text = name_1; 20 | for (var i_1 = 0; i_1 < text.length; i_1++) { 21 | if (text.charAt(i_1) >= 'a' && text.charAt(i_1) <= 'z') { 22 | return [{ 23 | range: range, 24 | severity: 3 25 | }]; 26 | } 27 | } 28 | } 29 | return []; 30 | } 31 | }; 32 | return rule; 33 | //2.13.7 -------------------------------------------------------------------------------- /src/lints/basic/125_remove_end_spaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'remove_unnecessary_blanks_at_the_end', 4 | desc: { cn: '在行尾删除不必要的空格', 5 | en: 'Remove unnecessary blanks at the end of a line.' }, 6 | tags: ['basic'], 7 | document: 125, 8 | check: function (document) { 9 | var totalLine = document.getLineSize(); 10 | for (var i = 0; i < totalLine; i++) { 11 | var lineRange = document.getLineRangeEx(i); 12 | var text = document.getTextEx(lineRange.start, lineRange.end); 13 | 14 | if (text.endsWith(' ') && !text.match(/^[ ]*$/)) { 15 | return [{ 16 | range: lineRange, 17 | severity: 3 18 | }]; 19 | } 20 | if(text.length>2 && text.endsWith('\r')){ 21 | let strBeforeR = text.substring(text.length-2,text.length-1); 22 | if(strBeforeR === ' '){ 23 | return [{ 24 | range:lineRange, 25 | severity: 3 26 | }]; 27 | } 28 | } 29 | } 30 | return []; 31 | } 32 | }; 33 | return rule; 34 | //2.8.6 -------------------------------------------------------------------------------- /src/lints/intermediate/113_no_arg_as_param.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const rule = { 4 | name: 'no_arg_as_param', 5 | desc: { cn: '不要用arg作为参数名,在低版本 Lua 中arg作为参数对象存在。', 6 | en: 'Do not use [arg] as a parameter, as in low versions of lua it is used as parameter object.' }, 7 | tags: ['intermediate'], 8 | document: 113, 9 | check: function (document) { 10 | let retRange; 11 | let nodes = document.ast.select(function (node) { 12 | let func = node.anyNode().asFunctionBody(); 13 | if (!func) { 14 | return false; 15 | } 16 | let params = func.getArguments(); 17 | for (let _i = 0, params_1 = params; _i < params_1.length; _i++) { 18 | let param = params_1[_i]; 19 | if (param.getText() == "arg") { 20 | retRange = param.getRange(); 21 | return true; 22 | } 23 | } 24 | return false; 25 | }, 1); 26 | if (nodes.length > 0) { 27 | return [{ 28 | range: retRange, 29 | severity: 2 30 | }]; 31 | } 32 | return []; 33 | } 34 | }; 35 | return rule; 36 | //2.5.4 -------------------------------------------------------------------------------- /src/lints/advance/127_no_semicolon_in_line.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'no_semicolon_in_line', 4 | desc: { cn: '不要通过;对语句分行,从而使一行中有多条语句。一行只能有一个语句', 5 | en: 'Do not use [;] to divide a line and make one line has multiple statements. One line can only have one statement.' }, 6 | tags: ['advance'], 7 | document: 127, 8 | check: function (document) { 9 | let nodes = document.ast.select(function (node) { 10 | let semi = node.anyNode().asSemicolon(); 11 | if (!semi) { 12 | return false; 13 | } 14 | let line = semi.getRange().end.line; 15 | let lineRange = document.getLineRangeEx(line); 16 | let start = semi.getRange().end; 17 | let end = lineRange.end; 18 | let text = document.getTextEx(start, end); 19 | if (!text.match(/^[ ]*$/) && text != '\r') { 20 | return true; 21 | } 22 | return false; 23 | }, 1); 24 | if (nodes.length > 0) { 25 | let node = nodes[0]; 26 | return [{ 27 | range: node.getRange(), 28 | severity: 2 29 | }]; 30 | } 31 | return []; 32 | } 33 | }; 34 | return rule; 35 | //2.10.1 -------------------------------------------------------------------------------- /src/lints/basic/104_duplicate_args.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "duplicate_arguments", 3 | tags: ['basic'], 4 | desc: { cn: '重复的参数名。', en: 'Duplicate argument in function.' }, 5 | document: 104, 6 | check(document) { 7 | let nodes = document.ast.select(node => { 8 | return node.isFunctionBody(); 9 | }); 10 | let nodesFounds = nodes.map(node => node.anyNode().asFunctionBody()); 11 | if (nodesFounds) { 12 | for (let node of nodesFounds) { 13 | let args = node.getArguments(); 14 | let keySet = new Map(); 15 | for (let arg of args) { 16 | let argName = arg.getText(); 17 | let argNode = keySet.get(argName); 18 | if (argNode) { 19 | return [{ 20 | range: argNode.getRange(), 21 | severity: 1 22 | }, { 23 | range: arg.getRange(), 24 | severity: 1 25 | }]; 26 | } 27 | else { 28 | keySet.set(arg.getText(), arg); 29 | } 30 | } 31 | } 32 | } 33 | return []; 34 | } 35 | }; -------------------------------------------------------------------------------- /src/lints/intermediate/103_no_this_arg.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const rule = { 4 | name: 'no_this_arg', 5 | desc: { cn: '在函数中要引用自身时使用self,不要使用自定义的this参数', 6 | en: 'Do not use [this] arg while refering to self, use [self:] instead' }, 7 | tags: ['intermediate'], 8 | document: 103, 9 | check: function (document) { 10 | let retRange; 11 | let ast = document.ast; 12 | if (ast) { 13 | let funcs = ast.select(function (node) { 14 | let func = node.anyNode().asFunctionBody(); 15 | if (!func) { 16 | return false; 17 | } 18 | let args = func.getArguments(); 19 | if (args.length == 0) { 20 | return false; 21 | } 22 | let firstName = args[0]; 23 | if (firstName.getText() == 'this') { 24 | retRange = firstName.getRange(); 25 | return true; 26 | } 27 | return false; 28 | }, 1); 29 | if (funcs.length > 0) { 30 | return [{ 31 | range: retRange, 32 | severity: 2 33 | }]; 34 | } 35 | } 36 | return []; 37 | } 38 | }; 39 | return rule; 40 | //2.3.3 -------------------------------------------------------------------------------- /src/lints/basic/133_no_single_name.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'no_single_name', 4 | desc: { cn: '避免使用单字母来命名函数或变量,命名时遵从见文知意原则', 5 | en: 'Avoid using one single letter to name a letiable or function. Make sure the name has a clear meaning.' }, 6 | tags: ['basic'], 7 | document: 133, 8 | check: function (document) { 9 | let names = document.ast.select(function (node) { 10 | let name = node.anyNode().asName(); 11 | if (!name) { 12 | return false; 13 | } 14 | if (name.getParent().isNamedFunction()) { 15 | if (name.getText().length <= 1) { 16 | return true; 17 | } 18 | } 19 | if (name.getParent().isAssign()) { 20 | if (name.getText().length <= 1) { 21 | return true; 22 | } 23 | } 24 | if(name.getParent().isField()){ 25 | if(name.getText().length<=1){ 26 | return true; 27 | } 28 | } 29 | return false; 30 | }, 1); 31 | if (names.length > 0) { 32 | let name_1 = names[0]; 33 | return [{ 34 | range: name_1.getRange(), 35 | severity: 2 36 | }]; 37 | } 38 | return []; 39 | } 40 | }; 41 | return rule; 42 | //2.13.2 -------------------------------------------------------------------------------- /src/lints/intermediate/101_table_property.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'table_property', 4 | desc: { cn: '尽量在表创建时,利用构造器对其属性进行赋值', 5 | en: 'Try define a table`s property while creating the table, not outside it.' }, 6 | tags: ['intermediate'], 7 | document: 101, 8 | check(document) { 9 | let nodes=document.ast.select(node=> { 10 | let assignNode=node.anyNode().asAssign(); 11 | if(!assignNode){ 12 | return false; 13 | } 14 | let assigns=assignNode.getAssigns(); 15 | let assign=assigns[0]; 16 | for(let assign of assigns){ 17 | let idx=assign.variable.anyNode().asNameIndex(); 18 | if(idx===undefined){ 19 | continue; 20 | } 21 | let curRange=idx.getName().getRange(); 22 | let def=document.getSymbolsByPos(curRange.start.line,curRange.start.character); 23 | let defRange=def[0].getSelectRange(); 24 | if(defRange.start.line==curRange.start.line){ 25 | return true; 26 | } 27 | } 28 | 29 | return false; 30 | },1); 31 | if(nodes.length>0){ 32 | return [{ 33 | range:nodes[0].getRange(), 34 | severity: 3 35 | }]; 36 | } 37 | return []; 38 | } 39 | } 40 | return rule; 41 | 42 | // 2.3.1 -------------------------------------------------------------------------------- /src/lints/basic/123_space_near_comma.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const rule = { 4 | name: 'space_near_comma', 5 | desc: { cn: '逗号之前避免使用空格,逗号之后需要使用空格。', 6 | en: 'Don not add a space in front of comma, do add a space after it.' }, 7 | tags: ['basic'], 8 | document: 123, 9 | check: function (document) { 10 | let retRange; 11 | let tok = document.parser.getToken(0); 12 | while (tok) { 13 | if (tok.getText() != ',') { 14 | tok = tok.next(); 15 | continue; 16 | } 17 | let fromTo = tok.getRange(); 18 | let tokRange = tok.getRangeEx(); 19 | let line = tokRange.start.line; 20 | let lineEnd = document.getLineRange(line).to; 21 | let textBefore = document.getText(fromTo.from - 1, fromTo.from); 22 | if (textBefore == ' ') { 23 | retRange = tokRange; 24 | break; 25 | } 26 | if (fromTo.to != lineEnd) { 27 | let textAfter = document.getText(fromTo.to, fromTo.to + 1); 28 | if (textAfter != ' ' && textAfter != '\r'){ 29 | retRange = tokRange; 30 | break; 31 | } 32 | } 33 | tok = tok.next(); 34 | } 35 | if (retRange) { 36 | return [{ 37 | range: retRange, 38 | severity: 2 39 | }]; 40 | } 41 | return []; 42 | } 43 | }; 44 | return rule; 45 | //2.8.3 -------------------------------------------------------------------------------- /src/lints/basic/105_duplicate_keys.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "duplicate_keys", 3 | tags: ['basic'], 4 | desc: { cn: '重复的表键值。', en: 'Duplicate keys in table.' }, 5 | document: 105, 6 | check(document) { 7 | var _a; 8 | let nodes = (_a = document.ast) === null || _a === void 0 ? void 0 : _a.select(node => { 9 | return node.isTable(); 10 | }); 11 | let nodesFound = nodes === null || nodes === void 0 ? void 0 : nodes.map(node => node.anyNode().asTable()); 12 | if (nodesFound) { 13 | for (let tabNode of nodesFound) { 14 | let fs = tabNode.getFields(); 15 | let keySet = new Map(); 16 | for (let f of fs) { 17 | let n = f.getKeyAsName(); 18 | if (n === undefined) { 19 | continue; 20 | } 21 | let keyName = n.getText(); 22 | let keyNode = keySet.get(keyName); 23 | if (keyNode) { 24 | return [{ 25 | range: keyNode.getRange(), 26 | severity: 1 27 | }, { 28 | range: n.getRange(), 29 | severity: 1 30 | }]; 31 | } 32 | else { 33 | keySet.set(n.getText(), n); 34 | } 35 | } 36 | } 37 | } 38 | return []; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/lints/intermediate/111_function_brackets.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'function_call_without_brackets', 4 | desc: { cn: '函数调用不能省略括号', 5 | en: 'Do use brackets while applying a function.' }, 6 | tags: ['intermediate'], 7 | document: 111, 8 | check: function (document) { 9 | let retRange; 10 | let nodes = document.ast.select(function (node) { 11 | let functionCall = node.anyNode().asApply(); 12 | if (!functionCall) { 13 | return false; 14 | } 15 | let params = functionCall.getParams(); 16 | if (params.length != 1) { 17 | return false; 18 | } 19 | let method = functionCall.getPrefixExp(); 20 | if (!method) { 21 | return false; 22 | } 23 | let methodRange = method.getRange(); 24 | let paramRange = params[0].getRange(); 25 | let left = document.offsetAt(methodRange.end); 26 | let right = document.offsetAt(paramRange.start); 27 | let betweenText = document.getText(left, right); 28 | if (betweenText.indexOf("(") == -1) { 29 | retRange = paramRange; 30 | return true; 31 | } 32 | return false; 33 | }, 1); 34 | if (retRange) { 35 | return [{ 36 | range: retRange, 37 | severity: 2 38 | }]; 39 | } 40 | return []; 41 | } 42 | }; 43 | return rule; 44 | //2.5.2 -------------------------------------------------------------------------------- /src/lints/advance/132_type_cast.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'type_cast', 4 | desc: { cn: '在语句的开头,利用内置函数进行类型转换 (tostring, tonumber, etc.)', 5 | en: 'Use integrated functions to execute type cast at the beginning of a statement (tostring, tonumber, etc.).' }, 6 | tags: ['advance'], 7 | document: 132, 8 | check: function (document) { 9 | let nodes = document.ast.select(function (node) { 10 | let BOP = node.anyNode().asBOP(); 11 | if (!BOP) { 12 | return false; 13 | } 14 | let left = BOP.getLeft(); 15 | let rights = BOP.getRights(); 16 | let leftType = left.getType(); 17 | if((leftType !== 'number')||(left.getType() !== 'string')){ 18 | return false; 19 | } 20 | for (let _i = 0, rights_1 = rights; _i < rights_1.length; _i++) { 21 | let right = rights_1[_i]; 22 | if (leftType != right.exp.getType()) { 23 | let text = BOP.getText(); 24 | if (text.indexOf('tostring') != -1 || text.indexOf('tonumber') != -1) { 25 | return false; 26 | } 27 | return true; 28 | } 29 | } 30 | return false; 31 | }, 1); 32 | if (nodes.length > 0) { 33 | return [{ 34 | range: nodes[0].getRange(), 35 | severity: 2 36 | }]; 37 | } 38 | return []; 39 | } 40 | }; 41 | return rule; 42 | //2.12 -------------------------------------------------------------------------------- /src/lints/basic/126_no_comma_starting_a_line.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'no_comma_starting_a_line', 4 | desc: { cn: '逗号后置', 5 | en: 'Put comma at the end, not the beginning of a line.' }, 6 | tags: ['basic'], 7 | document: 126, 8 | check: function (document) { 9 | let retRange; 10 | let tok = document.parser.getToken(0); 11 | while (tok) { 12 | if (tok == null || tok == void 0) { 13 | break; 14 | } 15 | if (tok.getText() != ',') { 16 | tok = tok.next(); 17 | continue; 18 | } 19 | let fromTo = tok.getRange(); 20 | let tokRange = tok.getRangeEx(); 21 | let line = tokRange.start.line; 22 | let lineEnd = document.getLineRange(line).to; 23 | let textBefore = document.getText(document.getLineRange(line).from, fromTo.from); 24 | if (textBefore.match(/^[ ]*$/)) { 25 | retRange = tokRange; 26 | break; 27 | } 28 | // if(fromTo.to != lineEnd){ 29 | // let textAfter=document.getText(fromTo.to,fromTo.to+1); 30 | // if(textAfter != ' '){ 31 | // retRange=tokRange; 32 | // } 33 | // break; 34 | // } 35 | tok = tok.next(); 36 | } 37 | if (retRange) { 38 | return [{ 39 | range: retRange, 40 | severity: 2 41 | }]; 42 | } 43 | return []; 44 | } 45 | }; 46 | return rule; 47 | //2.9.1 -------------------------------------------------------------------------------- /src/lints/basic/121_space_around_assign.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const rule = { 4 | name: 'space_around_assign', 5 | desc: { cn: '赋值操作符前后应当加空格。', 6 | en: 'There should be one and only one space ,before and after a `=`.' }, 7 | tags: ['basic'], 8 | document: 121, 9 | check: function (document) { 10 | let nodes=document.ast.select(node=>{ 11 | let assignNode=node.anyNode().asAssign(); 12 | if(!assignNode){ 13 | let fieldNode = node.anyNode().asField(); 14 | if(fieldNode){ 15 | let key = fieldNode.getKeyAsName() != undefined ? fieldNode.getKeyAsName():fieldNode.getKeyAsExp(); 16 | if(key){ 17 | let text=fieldNode.getText(); 18 | let idx=text.indexOf("="); 19 | if(idx == -1){ 20 | return false; 21 | } 22 | if(text[idx-1]!=" " || text[idx+1]!=" " || text[idx-2]==" " || text[idx+2]==" "){ 23 | return true; 24 | } 25 | } 26 | } 27 | return false; 28 | } 29 | 30 | let text=assignNode.getText(); 31 | let idx=text.indexOf("="); 32 | if(idx == -1){ 33 | return false; 34 | } 35 | if(text[idx-1]!=" " || text[idx+1]!=" " || text[idx-2]==" " || text[idx+2]==" "){ 36 | return true; 37 | } 38 | return false; 39 | },1); 40 | if(nodes.length>0){ 41 | let node=nodes[0]; 42 | return [{ 43 | range: node.getRange(), 44 | severity: 2 45 | }]; 46 | } 47 | return []; 48 | 49 | } 50 | }; 51 | return rule 52 | //2.8.2 -------------------------------------------------------------------------------- /src/lints/basic/114_not_too_many_circuits.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'not_too_many_circuits', 4 | desc: { cn: '函数中的嵌套尽量控制在3-4层,过多层嵌套会极大程度降低代码的可读性。', 5 | en: 'Do not have too many circuits within one function.' }, 6 | tags: ['basic'], 7 | document: 114, 8 | check(document) { 9 | let retRange; 10 | function checkBlock(document, block) { 11 | let blocks = [block]; 12 | let level = 0; 13 | 14 | while (blocks.length !== 0) { 15 | let subblocks = []; 16 | 17 | for (let i = 0; i < blocks.length; i++) { 18 | const parent = blocks[i].getParent(); 19 | 20 | if (parent && parent.isFunctionBody() && blocks[i]._id !== block._id) { 21 | checkBlock(document, blocks[i]); 22 | } 23 | else if (level < 5) { 24 | let stats = blocks[i].select(subnode => { 25 | return subnode.isBlock(); 26 | }); 27 | for(let state of stats){ 28 | let block = state.anyNode().asBlock(); 29 | block._id = state._id; 30 | subblocks.push(block); 31 | } 32 | } 33 | else { 34 | retRange = parent?.getRange(); 35 | } 36 | } 37 | blocks = subblocks; 38 | level++; 39 | } 40 | } 41 | 42 | let block = document.ast?.getBlock(); 43 | block && checkBlock(document,block); 44 | if(retRange){ 45 | return [{ 46 | range : retRange, 47 | severity: 2 48 | }]; 49 | } 50 | return []; 51 | } 52 | } 53 | return rule; 54 | //2.5.6 55 | -------------------------------------------------------------------------------- /src/lints/basic/107_unresolve_reference.js: -------------------------------------------------------------------------------- 1 | const rule = { 2 | name: "unresolved reference", 3 | tags: ['basic'], 4 | desc: { cn: '未声明引用。', en: 'Unresolved reference.' }, 5 | document: 107, 6 | check(document) { 7 | let ast = document.ast; 8 | if (ast == undefined) { 9 | return [] 10 | } 11 | 12 | let rets = []; 13 | 14 | let scope = document.getScope(); 15 | if (!scope) { 16 | return []; 17 | } 18 | document.ast.select(node=>{ 19 | if (node.isExp()) { 20 | if (node.isName()) { 21 | let nameNode = node.anyNode().asName(); 22 | let symInfo = document.getSymbolInfo(nameNode.getRange().start); 23 | if (!symInfo.type) { 24 | rets.push({ 25 | range: nameNode.getRange(), 26 | severity: 1 27 | }); 28 | return true; 29 | } 30 | } else if (node.isNameIndex()) { 31 | let nameIndexNode = node.anyNode().asNameIndex(); 32 | let names = nameIndexNode.getNames(); 33 | if (names.length === 0) { 34 | return false; 35 | } 36 | let lastName = names[names.length-1] 37 | let pos = lastName.getRange().start; 38 | let symInfo = document.getSymbolInfo(pos); 39 | if (!symInfo.type) { 40 | rets.push({ 41 | range: nameIndexNode.getRange(), 42 | severity: 1 43 | }); 44 | return true; 45 | } 46 | } 47 | } 48 | return false; 49 | },1) 50 | return rets; 51 | } 52 | }; 53 | 54 | return rule; 55 | -------------------------------------------------------------------------------- /src/lints/advance/142_table_index_from_zero.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'table_index_from_zero', 4 | desc: { cn: '禁止显式指定array下标从0开始,否则很多标准库的使用会不达预期。', 5 | en: 'Do not index an array from zero, otherwise many standard libraries wont meet the expectation.' }, 6 | tags: ['advance'], 7 | document: 142, 8 | check: function (document) { 9 | let retRange; 10 | let nodes=document.ast.select(node=>{ 11 | let tbl=node.anyNode().asTable(); 12 | if(tbl){ 13 | let fields=tbl.getFields(); 14 | if(fields.length<1){ 15 | return false; 16 | } 17 | for(let field of fields){ 18 | let key =field.getKeyAsExp(); 19 | if(key){ 20 | let text=key.getText(); 21 | if(text=='0'){ 22 | retRange=key.getRange(); 23 | return true; 24 | } 25 | } 26 | } 27 | } 28 | let assignNode=node.anyNode().asAssign(); 29 | if(assignNode){ 30 | let assigns=assignNode.getAssigns(); 31 | for(let ass of assigns){ 32 | let idx=ass.variable.anyNode().asIndex(); 33 | if(!idx){ 34 | continue; 35 | } 36 | let exp =idx.getIndexExp(); 37 | if(!exp){ 38 | continue; 39 | } 40 | if(exp.getText()=='0'){ 41 | retRange=exp.getRange(); 42 | return true; 43 | } 44 | } 45 | } 46 | 47 | return false; 48 | },1); 49 | if(nodes.length>0){ 50 | return[{ 51 | range: retRange, 52 | severity: 3 53 | }] 54 | } 55 | return []; 56 | } 57 | }; 58 | return rule; 59 | //3.2.21 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /src/lints/basic/122_space_around_BOP.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const rule = { 4 | name: 'space_around_BOP', 5 | desc: { cn: `赋值操作符、比较操作符、算术操作符、逻辑运算符等二元操作符的前后应当加一个空格。`, 6 | en: 'There should be one and only one space, before and after a binary operator.' }, 7 | tags: ['basic'], 8 | document: 122, 9 | check: function (document) { 10 | let retRange; 11 | let nodes = document.ast.select(function (node) { 12 | if (!node.isBOP()) { 13 | return false; 14 | } 15 | let BOP = node.anyNode().asBOP(); 16 | if (!BOP) { 17 | return false; 18 | } 19 | let nodeRange = BOP.getRange(); 20 | let start = nodeRange.start; 21 | let end = nodeRange.end; 22 | let rights = BOP.getRights(); 23 | let ops = rights.map(function (item) { return item.op; }); 24 | let content = document.getTextEx(start, end); 25 | let pos = 0; 26 | for (let _i = 0, ops_1 = ops; _i < ops_1.length; _i++) { 27 | let opr = ops_1[_i]; 28 | if(opr == "^"){ 29 | continue; 30 | } 31 | let opn = content.indexOf(opr, pos); 32 | if (opn != -1) { 33 | pos = opn + 1; 34 | let oplen = opr.length; 35 | if (content.charAt(opn - 1) != ' ' || content.charAt(opn + oplen) != ' ') { 36 | retRange = nodeRange; 37 | return true; 38 | } 39 | else if ((opn - 2 >= 0 && content.charAt(opn - 2) == " ") || (opn + oplen + 1 <= content.length && content.charAt(opn + oplen + 1) == " ")) { 40 | retRange = nodeRange; 41 | return true; 42 | } 43 | } 44 | } 45 | return false; 46 | }, 1); 47 | if (retRange) { 48 | return [{ 49 | range: retRange, 50 | severity: 2 51 | }]; 52 | } 53 | return []; 54 | } 55 | }; 56 | return rule 57 | //2.8.2 -------------------------------------------------------------------------------- /src/lints/advance/141_for_string_concat.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'for_string_concat', 4 | desc: { cn: '对于循环拼接字符串场景,使用table.concat', 5 | en: 'Use table.concat if you want to concat strings in a loop.' }, 6 | tags: ['advance'], 7 | document: 141, 8 | check: function (document) { 9 | function getConcator(forNode) { 10 | let concactExps = forNode.select(function (snode) { 11 | let bop = snode.anyNode().asBOP(); 12 | if (bop) { 13 | let rights = bop.getRights(); 14 | for (let _i = 0, rights_1 = rights; _i < rights_1.length; _i++) { 15 | let right = rights_1[_i]; 16 | if (right.op == '..') { 17 | return true; 18 | } 19 | } 20 | } 21 | return false; 22 | }); 23 | if (concactExps.length > 0) { 24 | let node = concactExps[0]; 25 | return node.getRange(); 26 | } 27 | return undefined; 28 | } 29 | let nodes = document.ast.select(function (node) { 30 | let forInNode = node.anyNode().asForIn(); 31 | if (forInNode) { 32 | let range = getConcator(forInNode); 33 | if (range) { 34 | return true; 35 | } 36 | } 37 | let forStepNode = node.anyNode().asForStep(); 38 | if (forStepNode) { 39 | let range = getConcator(forStepNode); 40 | if (range) { 41 | return true; 42 | } 43 | } 44 | let whileNode = node.anyNode().asWhile(); 45 | if (whileNode) { 46 | let range = getConcator(whileNode); 47 | if (range) { 48 | return true; 49 | } 50 | } 51 | let repeatNode = node.anyNode().asRepeat(); 52 | if (repeatNode) { 53 | let range = getConcator(repeatNode); 54 | if (range) { 55 | return true; 56 | } 57 | } 58 | return false; 59 | }, 1); 60 | if (nodes.length > 0) { 61 | let node = nodes[0]; 62 | let range = getConcator(node); 63 | if (range) { 64 | return [{ 65 | range: range, 66 | severity: 3 67 | }]; 68 | } 69 | } 70 | return []; 71 | } 72 | }; 73 | return rule; 74 | //3.1.2 -------------------------------------------------------------------------------- /src/lints/intermediate/134_for_in_underline.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'for_in_underline', 4 | desc: { cn: '循环中忽略的变量使用 _ 。', 5 | en: 'If a letiable is not used in a for loop, use _ as its name.' }, 6 | tags: ['intermediate'], 7 | document: 134, 8 | check: function (document) { 9 | let retRange; 10 | let nodes=document.ast.select(node => { 11 | if(!node.isBlock){ 12 | return false 13 | } 14 | let block=node.anyNode().asBlock() 15 | if(!block){ 16 | return false; 17 | } 18 | let parent=block.getParent(); 19 | if(!parent){ 20 | return false; 21 | } 22 | let ForInNode=parent.anyNode().asForIn(); 23 | if(!ForInNode){ 24 | return false; 25 | } 26 | let names=ForInNode.getNames(); 27 | if(names.length<1){ 28 | return false; 29 | } 30 | for(let firstName of names){ 31 | let text=firstName.getText(); 32 | if(text =="_"){ 33 | continue; 34 | } 35 | else{ 36 | let count=0 37 | let pos=firstName.getRange().start; 38 | let symbs=document.getSymbolsByPos(pos.line,pos.character); 39 | let symb=symbs[0]; 40 | let states=block.getStats() 41 | for(let sta of states){ 42 | let res=sta.select(node=>{ 43 | let pos=node.getRange().start; 44 | let sym=document.getSymbolsByPos(pos.line,pos.character)[0]; 45 | if(!sym){ 46 | return false; 47 | } 48 | if(sym.equal(symb)){ 49 | return true; 50 | } 51 | return false; 52 | }); 53 | if(res.length>0){ 54 | count++; 55 | } 56 | } 57 | if(count==0){ 58 | retRange = firstName.getRange(); 59 | return true; 60 | } 61 | } 62 | } 63 | 64 | return false; 65 | },1); 66 | if(nodes.length>0){ 67 | return [{ 68 | range: retRange, 69 | severity: 2 70 | }]; 71 | } 72 | 73 | return []; 74 | } 75 | }; 76 | return rule; 77 | //2.13.3 -------------------------------------------------------------------------------- /src/lints/advance/144_no_sharp_inFrontOf_hash.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'no_sharp_infrontof_hash', 4 | desc: { cn: '#tb 仅适用于array(键集为{1…n},缺一不可),hash禁止使用#关键字来获取表长度。', 5 | en: '[#tb] is suitable only for array, do not use # to get the length of a hash.' }, 6 | tags: ['advance'], 7 | document: 144, 8 | check: function (document) { 9 | let nodes=document.ast.select(node=>{ 10 | let uop=node.anyNode().asUOP(); 11 | if(!uop){ 12 | return false; 13 | } 14 | let op=uop.getOp(); 15 | if(op!='#'){ 16 | return false; 17 | } 18 | let tab=uop.getExp().anyNode().asTable(); 19 | if(tab){ 20 | if(tab.isHash()){ 21 | return true; 22 | } 23 | } 24 | 25 | let name=uop.getExp().anyNode().asName(); 26 | if(name){ 27 | let text=name.getText(); 28 | let pos=name.getRange().start; 29 | let symb=document.getSymbolsByPos(pos.line,pos.character)[0]; 30 | let symStart=symb.getRange().start 31 | let assNode=document.ast.getNodeByPos(symStart.line,symStart.character); 32 | let ass=assNode?.anyNode().asAssign(); 33 | if(ass){ 34 | let assigns=ass.getAssigns(); 35 | for(let a of assigns){ 36 | if(a.variable?.getText()==text){ 37 | let tbl=a.exp.asTable(); 38 | if(tbl){ 39 | if(tbl.isHash()){ 40 | return true; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | } 48 | 49 | let idx=uop.getExp().anyNode().asNameIndex(); 50 | if(idx){ 51 | let Names=idx.getNames(); 52 | let name=Names[Names.length-1]; 53 | let pos=name.getRange().start; 54 | let symb=document.getSymbolsByPos(pos.line,pos.character)[0]; 55 | let symStart=symb.getRange().start 56 | let Node=document.ast.getNodeByPos(symStart.line,symStart.character); 57 | let fieldNode=Node?.getParent()?.anyNode().asField(); 58 | if(fieldNode){ 59 | let val=fieldNode.getValue(); 60 | if(val){ 61 | let tbl=val.asTable(); 62 | if(tbl){ 63 | return tbl.isHash(); 64 | } 65 | } 66 | } 67 | } 68 | return false; 69 | },1); 70 | 71 | if(nodes.length>0){ 72 | return[{ 73 | range:nodes[0].getRange(), 74 | severity:3 75 | }]; 76 | } 77 | 78 | return []; 79 | } 80 | }; 81 | return rule; 82 | //3.2.6 -------------------------------------------------------------------------------- /src/lints/basic/136_comments.js: -------------------------------------------------------------------------------- 1 | return { 2 | name: "comments", 3 | tags: ['basic'], 4 | desc: { cn: '对大段内容进行注释时,使用--[[]]。', en: 'use --[[]], if comment is more than 3 lines.' }, 5 | document: 136, 6 | check(document) { 7 | let tok = document.parser.getToken(0); 8 | let singleLineCount = 0; 9 | if (tok) { 10 | let comments = tok.getComments(); 11 | if(comments.length > 0){ 12 | let startIndex = 0; 13 | let ranges = tok.getCommentRanges(); 14 | for (let i = 0; i < comments.length; i++) { 15 | const com = comments[i]; 16 | if (com.startsWith('--[[') || com.startsWith('-- [[') || com.startsWith('---')) { 17 | singleLineCount = 0; 18 | if(i + 1 < comments.length-1){ 19 | startIndex = i+1 20 | } 21 | continue; 22 | } 23 | 24 | if(ranges[i]){ 25 | if(i>0 && ranges[i].start?.line !== ranges[i-1].start?.line+1){ 26 | singleLineCount = 0; 27 | if(i + 1 < comments.length-1){ 28 | startIndex = i+1 29 | } 30 | continue; 31 | } 32 | } 33 | singleLineCount++; 34 | if (singleLineCount > 3) { 35 | let start = ranges[startIndex].start; 36 | let end = ranges[ranges.length-1].end; 37 | return [{ 38 | range:{start,end}, 39 | severity:2 40 | }]; 41 | } 42 | } 43 | } 44 | 45 | } 46 | while (tok) { 47 | singleLineCount = 0; 48 | let comments = tok === null || tok === void 0 ? void 0 : tok.getCommentsPost(); 49 | if(comments && comments.length > 0){ 50 | let startIndex = 0; 51 | let ranges = tok.getCommentRangesPost(); 52 | for (let i = 0; i < comments.length; i++) { 53 | const com = comments[i]; 54 | if (com.startsWith('--[[') || com.startsWith('-- [[') || com.startsWith('---')) { 55 | singleLineCount = 0; 56 | if(i + 1 < comments.length-1){ 57 | startIndex = i+1 58 | } 59 | continue; 60 | } 61 | if(ranges[i]){ 62 | if(i>0 && ranges[i].start?.line !== ranges[i-1].start?.line+1){ 63 | singleLineCount = 0; 64 | if(i + 1 < comments.length-1){ 65 | startIndex = i; 66 | } 67 | continue; 68 | } 69 | } 70 | 71 | singleLineCount++; 72 | if (singleLineCount > 3) { 73 | let start = ranges[startIndex].start; 74 | let end = ranges[ranges.length-1].end; 75 | return [{ 76 | range:{start,end}, 77 | severity:2 78 | }]; 79 | } 80 | } 81 | } 82 | 83 | tok = tok.next(); 84 | } 85 | return []; 86 | } 87 | 88 | }; 89 | //2.14.4 90 | -------------------------------------------------------------------------------- /src/lints/advance/145_use_ipairs_for_array.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const rule = { 3 | name: 'use_ipairs_for_array', 4 | desc: { cn: 'ipairs、#关键字搭配for循环只能用来遍历array,而pairs或者next可以遍历任意集合。', 5 | en: 'ipairs, # plus a for loop can only be used to iterate arrays, pairs or next can iterate any set.' }, 6 | tags: ['advance'], 7 | document: 145, 8 | check: function (document) { 9 | let retRange; 10 | let nodes=document.ast.select(node=>{ 11 | let forInNode =node.anyNode().asForIn(); 12 | if(!forInNode){ 13 | return false; 14 | } 15 | let exps=forInNode.getExps(); 16 | let exp=exps[0]; 17 | if(!exp){ 18 | return false; 19 | } 20 | let text=exp.getText(); 21 | if(text.indexOf('ipairs') != -1){ 22 | let app=exp.anyNode().asApply(); 23 | if(app){ 24 | let param = app.getParams()[0]; 25 | let name=param.asName(); 26 | if(name){ 27 | let text=name.getText(); 28 | let pos=name.getRange().start; 29 | let symb=document.getSymbolsByPos(pos.line,pos.character)[0]; 30 | if(!symb){ 31 | return false; 32 | } 33 | let symStart=symb.getRange().start 34 | let assNode=document.ast.getNodeByPos(symStart.line,symStart.character); 35 | let ass=assNode?.anyNode().asAssign(); 36 | if(ass){ 37 | let assigns=ass.getAssigns(); 38 | for(let a of assigns){ 39 | if(a.variable?.getText()==text){ 40 | let tbl=a.exp.asTable(); 41 | if(tbl){ 42 | if(tbl.isHash()){ 43 | retRange=param.getRange(); 44 | return true; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | } 52 | 53 | let idx=param.asNameIdx(); 54 | if(idx){ 55 | let Names=idx.getNames(); 56 | let name=Names[Names.length-1]; 57 | let pos=name.getRange().start; 58 | let symb=document.getSymbolsByPos(pos.line,pos.character)[0]; 59 | if(!symb){ 60 | return false; 61 | } 62 | let symStart=symb.getRange().start 63 | let Node=document.ast.getNodeByPos(symStart.line,symStart.character); 64 | let fieldNode=Node?.getParent()?.anyNode().asField(); 65 | if(fieldNode){ 66 | let val=fieldNode.getValue(); 67 | if(val){ 68 | let tbl=val.asTable(); 69 | if(tbl){ 70 | if(tbl.isHash()){ 71 | retRange=param.getRange(); 72 | return true; 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | return false; 82 | },1); 83 | if(nodes.length>0){ 84 | return [{ 85 | range: retRange, 86 | severity: 3 87 | }]; 88 | } 89 | 90 | return []; 91 | } 92 | }; 93 | return rule; 94 | //3.2.7 -------------------------------------------------------------------------------- /doc/standard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 |
16 |

Lua Booster 内置编码标准

17 | 18 |

综述

19 | 20 |

Lua booster插件内置了许多默认静态检查规则,本文档内容是相关的编码规范。用户可以查看此文档中的文字描述和用例以详细了解我们的各项规则。

21 | 22 |

Lua booster has many lint rules within. This document contains those related standards. Users can read this 23 | document and learn about our rules.

24 | 25 |

各项规则后会有标签 basic, intermediate, 26 | 或advanced表明其重要度。baisc级别包含基本的规范,如空格、命名等内容。advanced级别类似编码技巧。intermediate级别则处于两者之间。 27 |

28 | 29 |

There are tags behind each title indicating the importance of the rules. The basic level 30 | contains basic rules, advanded level contains rules like coding technics, 31 | intermediate contains rules between them. 32 |

33 | 34 |
35 | 36 |

1. 定义[Definition]

37 | 38 |

101

39 | 40 | 67 | 68 |

102

69 | 70 | 77 | 78 |
    -- bad
 79 |         local user = {
 80 |             check_login = function() 
 81 |                 -- ...stuff...
 82 |             end
 83 |         }
 84 |     
 85 |         -- good
 86 |         local function check_login()
 87 |             -- ...stuff...
 88 |         end
 89 |     
 90 |         local user = {
 91 |             online_status = check_login
 92 |         }
 93 |     
94 | 95 |

103

96 | 97 | 104 | 105 |
    -- bad
106 |         local file_path = {
107 |             full_path = function(this)
108 |                 return string.format("%s%s%s", this.current_dir, "/", this.file_name)
109 |             end
110 |         }
111 |     
112 |         -- good
113 |         local file_path = {}
114 |     
115 |         function file_path:full_path()
116 |             return string.format("%s%s%s", self.current_dir, "/", self.file_name)
117 |         end
118 |     
119 | 120 |

104

121 | 122 | 129 | 130 |
    --bad
131 |         local function func(name, name)
132 |             ---
133 |         end
134 |     
135 |         --good
136 |         local function func(name1, name2)
137 |             -- do something
138 |         end
139 |     
140 | 141 |

`

142 | 143 |

105

144 | 145 | 152 | 153 |
    --bad
154 |         local tab = {
155 |             age = 10
156 |             age = 60
157 |         }
158 |         -- good
159 |         local tab = {
160 |             age = 10,
161 |             weight = 60
162 |         }
163 |     
164 | 165 |

106

166 | 167 | 174 | 175 |

107

176 | 177 | 198 | 199 |

108

200 | 201 | 208 | 209 |
    -- bad
210 |         a = 10
211 |     
212 |         -- good
213 |         local a = 10
214 |     
215 | 216 |

109

217 | 218 | 225 | 226 |
    -- okay
227 |         if flag then return false end
228 |     
229 |         -- good
230 |         if flag then
231 |             return false
232 |         end
233 |     
234 |         -- bad
235 |         if msg and msg[1] and msg[1] == "exec_rsp" and msg[2] and msg[2] == "success" and msg[3] and msg[3] >= 75 then do_complicated_function() end
236 |     
237 |         -- good
238 |         if msg and msg[1] and msg[1] == "exec_rsp" and msg[2] and msg[2] == "success" and msg[3] and msg[3] >= 75 then
239 |             do_complicated_function() 
240 |         end
241 |     
242 | 243 |
244 | 245 |

2. 函数相关[Function]

246 | 247 |


248 | 249 |

111

250 | 251 | 258 | 259 |
    -- bad
260 |         foo "param"
261 |         bar { user_id = 1 }
262 |     
263 |         -- good
264 |         foo("param")
265 |         bar({ user_id = 1 })
266 |     
267 | 268 |

112

269 | 270 | 277 | 278 |
    -- bad
279 |         local battle_info = function(user_id, battle_id)
280 |             -- ...stuff...
281 |         end
282 |     
283 |         -- good
284 |         local function battle_info(user_id, battle_id)
285 |             -- ...stuff...
286 |         end
287 |     
288 | 289 |

113

290 | 291 | 299 | 300 |
    -- bad
301 |         local function battle_info(user_id, battle_id, arg) 
302 |             -- ...stuff...
303 |         end
304 |     
305 |         -- good
306 |         local function battle_info(user_id, battle_id, ...)
307 |             -- ...stuff...
308 |         end
309 |     
310 | 311 |

114

312 | 313 | 320 | 321 |
    -- bad
322 |         if a = 10 do
323 |             while(a > 0) do
324 |             local b = 1
325 |                 repeat
326 |                     print('aaa')
327 |                     b = b + 1
328 |                     if(a > 5) then
329 |                         a = a - 1
330 |                         if true then
331 |                             print('a - b + ')
332 |                         end
333 |                     end
334 |                 until b = a
335 |             end
336 |         end
337 |     
338 | 339 |
340 | 341 |

3. 空格[Space]

342 | 343 |


344 | 345 |

121

346 | 347 | 354 | 355 |
    -- bad
356 |         local a=10
357 |         local b =10
358 |         local c= 10
359 |         local d  =  10
360 |     
361 |         -- good
362 |         local a = 10
363 |     
364 | 365 |

122

366 | 367 | 374 | 375 |
    -- bad
376 |         local score=1
377 |         score = score-1
378 |         score = score*1
379 |         local title = "str1".."str2"
380 |     
381 |         -- good
382 |         local score = 1
383 |         score = score - 1
384 |         score = score * 1
385 |         local title = "str1" .. "str2"
386 |     
387 |         -- detailed example
388 |         local a = 1
389 |         local b = 2
390 |         local c = a + b
391 |     
392 |         -- 算术运算符
393 |         c = a - b
394 |         c = a * b
395 |         c = a / b
396 |         c = a % b
397 |         c = a^2                   -- 乘幂运算符前后不加空格
398 |         c = -a
399 |     
400 |         -- 关系运算符
401 |         if a == b then
402 |         end
403 |     
404 |         if a ~= b then
405 |         end
406 |     
407 |         if a < b then
408 |         end
409 |     
410 |         if a > b then
411 |         end
412 |     
413 |         if a <= b then
414 |         end
415 |     
416 |         if b >= a then
417 |         end
418 |     
419 |         -- 逻辑运算符
420 |         a = true
421 |         b = true
422 |     
423 |         if a and b then
424 |         end
425 |     
426 |         if a or b then
427 |         end
428 |     
429 |         if not (a and b) then
430 |         end
431 |     
432 |         -- 其他运算符
433 |         a = "a"
434 |         b = "b" 
435 |         print("a .. b:", a .. b)
436 |         c = #a                    -- 取长度运算符前后不加空格
437 |     
438 | 439 |

123

440 | 441 | 448 | 449 |
    -- bad
450 |         local array = {1,2,3}
451 |         array = {1 , 2 , 3}
452 |         array = {1 ,2 ,3}
453 |     
454 |         -- good
455 |         local array = { 1, 2, 3 }
456 |     
457 | 458 |

124

459 | 460 | 467 | 468 |
    -- bad
469 |         local dynamic_module = require("dynamic_module")
470 |         local lua_class_base  = {}
471 |         -- ...stuff...
472 |         return lua_class_base
473 |         ```
474 |     
475 |         ```lua
476 |         -- good
477 |         local dynamic_module = require("dynamic_module")
478 |         local lua_class_base  = {}
479 |         -- ...stuff...
480 |         return lua_class_base
481 |         -- one more empty line
482 |     
483 | 484 |

125

485 | 486 | 493 | 494 |
    -- bad
495 |         local a = 20     -- a lot of spaces at the end
496 |     
497 |         -- good
498 |         local a = 20
499 |     
500 | 501 |

126

502 | 503 | 515 | 516 |
    -- bad
517 |         local user = {
518 |             name = "Robin"
519 |             , level = 56
520 |             , grade = "gold"
521 |         }
522 |     
523 |         -- good
524 |         local user = {
525 |             name = "Robin",
526 |             level = 56,
527 |             grade = "gold"
528 |         }
529 |     
530 |         -- also good
531 |         local user = {
532 |             name = "Robin",
533 |             level = 56,
534 |             grade = "gold",
535 |         }
536 |     
537 | 538 |

127

539 | 540 | 549 | 550 |
    -- bad
551 |         local device = "iphone";
552 |         local system = "ios"; local network = "WiFi"
553 |     
554 |         -- good
555 |         local device = "iphone"
556 |         local system = "ios"
557 |         local network = "WiFi"
558 |     
559 | 560 |
561 | 562 |

3.杂项[ECT]

563 | 564 |


565 | 566 |

131

567 | 568 | 575 | 576 |
    -- bad
577 |         if item ~= nil then
578 |             -- ...stuff...
579 |         end
580 |     
581 |         -- good
582 |         if item then
583 |             -- ...stuff...
584 |         end
585 |     
586 | 587 |

132

588 | 589 | 598 | 599 |
    -- bad
600 |         local total_score = review_score .. ""
601 |     
602 |         -- good
603 |         local total_score = tostring(review_score)
604 |     
605 |         local input_level = "7"
606 |     
607 |         -- bad
608 |         local level = input_level * 1
609 |     
610 |         -- good
611 |         local level = tonumber(input_level)
612 |     
613 | 614 |

133

615 | 616 | 624 | 625 |
-- bad
626 |         local function c() 
627 |             -- ...stuff...
628 |         end
629 |     
630 |         -- good
631 |         local function check_param() 
632 |             -- ...stuff...
633 |         end
634 |     
635 | 636 |

`

637 | 638 |

134

639 | 640 | 647 | 648 |
    for _, user in pairs(users) do
649 |             -- ...stuff...
650 |         end
651 |     
652 | 653 |

135

654 | 655 | 662 | 663 |
    -- bad
664 |         list_default_width = 100
665 |     
666 |         -- good
667 |         LIST_DEFAULT_WIDTH = 100
668 |     
669 | 670 |

136

671 | 672 | 679 | 680 |
    --[[
681 |         local function get_score(user) 
682 |             -- set the default score to 'no score' 
683 |             local score = user.score or "no score"
684 |             return score
685 |         end
686 |         ]]
687 |     
688 | 689 |

137

690 | 691 | 698 | 699 |
700 | 701 |

4. 编码技巧[Coding Technic]

702 | 703 |


704 | 705 |

141

706 | 707 | 714 | 715 |
    local sub_str = { 
716 |             "a",
717 |             "b",
718 |             "c",
719 |             "d",
720 |             "e",
721 |             "f"
722 |         }
723 |         local res_str = ""
724 |     
725 |         -- bad
726 |         for _, value in ipairs(sub_str) do
727 |             res_str = res_str .. value
728 |         end
729 |     
730 |         -- good
731 |         res_str = table.concat(sub_str)
732 |     
733 | 734 |

142

735 | 736 | 745 | 746 |
    -- bad    
747 |         local tb = { [0] = "one", "two", "three"}
748 |     
749 |         -- good
750 |         local tb = { "one", "two", "three" }
751 |     
752 | 753 |

143

754 | 755 | 762 | 763 |
    local tb = {}
764 |         -- bad
765 |         if tb == nil then return nil end
766 |     
767 |         -- good
768 |         local index = next(tb)
769 |         if index == nil then return nil end
770 |     
771 | 772 |

144

773 | 774 | 782 | 783 |
    local person = {
784 |             name = "bob",
785 |             age = 12,
786 |             hobbies = { "eat", "sleep" }
787 |         }
788 |     
789 |         -- bad
790 |         local len = #person
791 |     
792 |         -- good
793 |         local len = #person.hobbies
794 |     
795 | 796 |

145

797 | 798 | 808 |
809 | 810 | 811 | 812 | --------------------------------------------------------------------------------