关于 sasjs-lint
sasjs-lint 是介绍使用 @sasjs/lint 检查和格式化 SAS 代码的技能参考。它说明配置、规则、格式化器行为,以及 CLI、VS Code 扩展、Server Studio 和库 API 中的用法。
主要功能
- 01SAS 源文件检查规则
- 02项目级和文件级配置
- 03严重级别设置与诊断行为
- 04支持的自动修复格式化器
- 05通过 CLI、VS Code、Server Studio 或 API 使用
适用场景
- 01为 SAS 项目配置 .sasjslint
- 02理解或调整 lint 警告
- 03将代码检查或格式化加入编辑器工作流或 CI
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sasjs/ skills --skill sasjs-lint安装后在 Claude Code 中输入/sasjs-lint确认已加载。
详细介绍
@sasjs/lint
@sasjs/lint 是 SAS 代码的 lint 和格式化引擎。它是一个不依赖 SAS 的 TypeScript 库——会将 .sas 文件作为文本读取——也是 sasjs lint、SASjs VS Code 扩展的 Problems 面板以及 SASjs Server Studio 编辑器背后的唯一引擎。
本技能介绍规则、.sasjslint 文件、格式化程序及其调用方式。不涉及 SAS 语言本身(参见 sas)或 CLI 的其他部分(参见 sasjs-cli)。
以下规则和默认值已根据 @sasjs/lint 4.5.0 进行核对,该版本由 @sasjs/cli 6.2.5 捆绑。该库会不断增加规则,因此固定使用旧版本的界面可能不包含所有规则——参见 运行位置。
适用场景
- 编写或修改
.sasjslint文件 sasjs lint运行报告警告,需要了解其含义或如何屏蔽- 将 lint 添加到 CI 流水线或 git 提交前钩子
- 运行格式化程序(
sasjs lint fix、保存时格式化、formatText) - 说明编辑器中的某条规则为何触发或未触发
运行位置
| 界面 | 调用方式 | 备注 |
|---|---|---|
| SASjs CLI | sasjs lint、sasjs lint find、sasjs lint fix、sasjs lint init | find 是默认子命令。init 会在项目根目录写入一个包含完整默认配置的 .sasjslint |
VS Code 扩展(sasjs/sasjs-for-vscode,发布于 Open VSX) | Problems 面板(ctrl+shift+M)、Format Document、保存时格式化 | 配置优先级:项目 .sasjslint > 设置 sasjs-for-vscode.lintConfig > ~/.sasjslint > 默认值 |
| SASjs Server Studio | POST /SASjsApi/code/lint 和 POST /SASjsApi/code/format,请求体 { code, filePath } | 规则取自 SASjs Drive 上文件所在文件夹或其上级目录中最近的 .sasjslint,逐级向上查找到 Drive 根目录。首次启动时会在 Drive 根目录预置 .sasjslint,并在文件树中显示为 /.sasjslint |
| 库 | lintText、lintFile、formatText、formatFile 以及对应的文件夹/项目版本 | 参见下方的 API 部分 |
这四种界面都调用相同的规则。CLI、扩展和服务各自固定使用一个版本的 @sasjs/lint,因此,只有在某个界面升级其依赖项后,新加入库中的规则才会出现在该界面中——告知用户规则应在编辑器中触发前,请先检查固定使用的版本。
配置解析
未传入配置时,库会按以下顺序解析配置:
- 项目根目录中的
.sasjslint(库会逐级向上查找项目) - 用户主目录中的
.sasjslint DefaultLintConfiguration——内置默认值
未指定的设置会采用默认值,完全没有 .sasjslint 文件的项目也一样。DefaultLintConfiguration 从 @sasjs/lint/utils/getLintConfig 导出,也是 sasjs lint init 写入的内容。
.sasjslint 文件
{
"noTrailingSpaces": true,
"maxLineLength": 100,
"noSingleAsteriskComments": true,
"severityLevel": {
"hasDoxygenHeader": "warn",
"noEncodedPasswords": "error"
}
}
规则和默认值
分为两组,关闭规则的方式取决于它所属的组。
默认启用 — 使用 false 关闭(另有说明的除外):
| 设置 | 默认值 | 严重级别 | 检查内容 |
|---|---|---|---|
hasDoxygenHeader | true | WARNING | 文件是否以 /** Doxygen 头注释开头 |
hasMacroNameInMend | true | WARNING | %mend 是否包含宏名称 |
hasMacroParentheses | true | WARNING | 宏定义是否带括号,且 ( 前没有空格 |
noNestedMacros | true | WARNING | 是否没有在另一个宏内部定义 %macro |
strictMacroDefinition | true | WARNING | 参数名称中是否没有空格(%macro m(my var);),是否没有无法识别的选项(%macro m()/nonsense;) |
noUndeclaredMacros | true | WARNING | 文件调用的每个宏是否都在文件中或其头注释中声明 |
noUnusedMacros | true | WARNING | <h4> SAS Macros </h4> 中的每个宏是否都实际被调用 |
noNestedCommentsInHeader | true | WARNING | 开头的 Doxygen 头注释中是否没有 */。SAS 会将其视为注释结束标记;其后的所有内容,包括 @param 行和用法示例,都会作为 SAS 代码编译 |
noBannedStrings | true | WARNING | 每行是否都不包含配置的字符串。默认检查类似日志内容的词语 error 、error:、warning 、warning:。不区分大小写,注释也会检查 |
noTrailingSpaces | true | WARNING | 行尾是否没有空白字符 |
noTabs | true | WARNING | 是否没有制表符。别名:noTabIndentation(已弃用) |
noGremlins | true | WARNING | 是否没有零宽字符或其他非标准字符 |
indentationMultiple | 2 | WARNING | 行首空格数是否能被该数字整除 |
lowerCaseFileNames | true | WARNING | 文件名是否为小写(*nix 上的自动调用要求文件名为小写) |
noSpacesInFileNames | true | WARNING | 文件名中是否没有空格 |
noEncodedPasswords | true | ERROR | 是否没有 {sas00X} 或 {sasenc} 编码密码 |
maxLineLength | 80 | WARNING | 行长度。设为 0 可关闭 |
默认关闭 — 使用 true 启用:
| 设置 | 默认值 | 严重级别 | 检查内容 |
|---|---|---|---|
noSingleAsteriskComments | false | WARNING | 是否没有 * text; 形式的注释语句 |
noUnusedLibnames | false | WARNING | 文件是否没有分配后从未使用的 libref |
hasRequiredMacroOptions | false | WARNING | 宏是否包含 requiredMacroOptions 中的选项 |
lineEndings | "off" | WARNING | 行尾是否符合 lf 或 crlf |
辅助设置(非规则):
| 设置 | 默认值 | 用途 |
|---|---|---|
ignoreList | [] | 要跳过的路径。.gitignore 中的文件也会跳过 |
allowedGremlins | [] | 4 位十六进制字符串,例如 "0x0080" |
defaultHeader | 见下文 | 应用于没有 /** 头注释的文件的头注释模板 |
severityLevel | {} | 按规则覆盖 "warn" / "error" |
maxHeaderLineLength | 80 | 头注释中的行长限制 |
maxDataLineLength | 80 | datalines / cards / parmcards 记录中的行长限制 |
requiredMacroOptions | [] | hasRequiredMacroOptions 要求的选项 |
ignoredLibnames | [] | noUnusedLibnames 不检查的 libref |
ignoredMacros | ["webout"] | noUndeclaredMacros 视为已声明的宏名称。设置后会替换默认值 — 见下文 |
bannedStrings | ["error ", "error:", "warning ", "warning:"] | noBannedStrings 要报告的字符串。设置后会替换默认值 — 见下文 |
严重级别和退出代码
每条规则有三种状态:关闭、WARNING 或 ERROR。sasjs lint 仅在报告 ERROR 时返回非零退出代码 — 警告不会改变退出代码,因此只产生警告的文件仍会通过基于退出代码判断的钩子或流水线。noEncodedPasswords 是唯一默认严重级别为 ERROR 的规则。
severityLevel 可按规则名称提升或降低严重级别,只接受 "warn" 和 "error";其他值会被忽略,且不会提示:
{
"severityLevel": {
"maxLineLength": "error",
"hasDoxygenHeader": "warn"
}
}
文件级覆盖
文件通过其 Doxygen 头部中的 @sasjslint 块调整适用于自身的规则:
/**
@file
@brief Calls the settlement API
@sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}
<h4> SAS Macros </h4>
@li mf_trim.sas
**/
- 标量会替换原值,数组会追加(
allowedGremlins、requiredMacroOptions、ignoredLibnames会增加条目而非替换),对象会逐键合并(severityLevel)。 - 覆盖项也适用于格式化程序,因此
sasjs lint fix会遵循它——关闭noUnusedMacros的文件会保留否则会被删除的头部条目。 - 只读取头部,且只识别紧跟标签并以
{开头的行。格式错误或未闭合的块会被忽略,不会引发错误。 - 头部设置优先于调用方传入的配置。
ignoreList是头部不能使用的唯一设置:它会在读取文件前进行评估。- 请将该块放在
<h4> SAS Macros </h4>部分之外,格式化程序会重写该部分。
ignoredMacros 和 bannedStrings
这两项都是列表,其项目级值会替换内置默认值,而不是在默认值上追加:显式设置 ignoredMacros 会移除 webout,除非重新列出;"bannedStrings": [] 则完全不报告任何内容。单文件的 @sasjslint 块恰好相反——其中的数组会追加,因此文件会在不必重述项目列表的情况下添加条目。
ignoredMacros 仅由 noUndeclaredMacros 读取。它对 noUnusedMacros 没有作用,因此无法用这种方式消除相反的警告——在头部声明但从未调用的宏;请移除该条目,或为该文件关闭该规则。
ignoredMacros 的用途:通过提供程序引入的宏
Lint 工具只读取一个文件和配置。它不了解构建的其他部分,而 sasjs lint 不会从 sasjsconfig.json 向它传递任何信息。因此,调用宏的文件必须声明该宏,即使编译过程通过其他方式提供了该宏:
- serviceInit 程序(目标配置中的
serviceInit,会编译到每个服务中)会在自身的<h4> SAS Macros </h4>中声明宏文件——服务主体可以调用这些宏而无需声明,但每次调用都会收到一次警告; sasjs test中的 testSetUp / testInit 程序,结构相同;- 编译器自行注入的内容:内置情形是
%webout,这也是默认列表包含webout的原因。
对于这些情况,项目的 .sasjslint 是合适的配置位置:
{
"ignoredMacros": ["webout", "mpeinit", "mpeterm"]
}
请注意两点。由于列表会替换默认值,请重新列出 webout;否则,每个输出流的服务中都会报告 %webout。此外,项目级条目也会在宏确实缺失的文件中关闭检查,因此只应列出那些能被所有可能调用它的文件获取的宏;如果提供程序只覆盖少数文件,而非全部文件,那么使用文件级 @sasjslint {"ignoredMacros": [...]} 会更精准。
bannedStrings
四个默认值都是看起来像日志消息的词:注释或 %put 中出现这些词时,它们会出现在 SAS 日志的 grep 结果中,也会出现在任何基于此类 grep 的日志扫描器结果中,因此真正的问题很容易淹没在仅仅是文字说明的匹配项里。匹配不区分大小写,按字符串长度从长到短进行,且不会对重叠的范围重复报告;同时会跳过两类内容:
datalines/cards/parmcards记录,其中的文本是数据而非代码;- 包含
@sasjslint块的那一行,因此文件可以在自己的覆盖设置中配置禁用字符串。
请改写说明文字。如果文本必须原样保留在日志中,请用宏函数拆开该词:%put %str(WARN)ING: message 会打印 WARNING: message,且不再包含该字面字符串。Doxygen 的 @warning 标签属于内容而非说明文字,因此此类文件需要设置 @sasjslint {"noBannedStrings": false}。
格式化器
格式化器会在保存时应用可修复的规则。目前已实现:
- 将宏名称添加到
%mend - 当文件中没有 Doxygen 头部时,添加一个模板
- 将文件使用的宏添加到
<h4> SAS Macros </h4>,去重并排序 - 从该部分移除文件未使用的宏
- 移除行尾空格
尚未实现(请勿承诺这些功能):将制表符转换为空格、移除 gremlin 字符、修正换行符、将单星号注释转换为块注释。
defaultHeader 使用 {lineEnding} 作为换行符占位符——字面值 \n 不起作用:
{
"defaultHeader": "/**{lineEnding} @file{lineEnding} @brief Our Company Brief{lineEnding}**/"
}
库 API
import { lintText, lintFile, formatText, formatFile, LintConfig, Severity } from '@sasjs/lint'
const diagnostics = await lintText(code, config) // config optional
const fixed = await formatText(code, config) // config optional
lintText(text, configuration?)、lintFile(path, configuration?)、lintFolder、lintProjectformatText(text, configuration?)、formatFile、formatFolder、formatProjectnew LintConfig(json)从普通对象构建配置;config.override(json)返回合并了覆盖项的新配置Diagnostic包含message、lineNumber、startColumnNumber、endColumnNumber和severitySeverity为Info(0)、Warning(1)、Error(2)
当调用方知道规则时,传入显式的 LintConfig 很重要——对没有磁盘路径的缓冲区进行检查的服务或编辑器,无法依赖工作目录遍历。
常见问题
- 不同规则中,“关闭”的含义不同。 默认开启的规则使用
false关闭;maxLineLength使用0关闭;lineEndings使用"off"关闭;默认关闭的规则会忽略false(效果与不设置相同),需要设为true才能开启。设置错误是规则“无法生效”最常见的原因。 - 当
maxLineLength关闭时,maxHeaderLineLength和maxDataLineLength不起作用,因为它们的作用范围受其约束。有效限制取两个值中较高的那个,因此将其中一个设为低于maxLineLength的值会被静默忽略。 - 无论是
ignoredMacros还是bannedStrings,显式列表都会替换默认列表。 只有未设置该项时才会使用默认值,因此"ignoredMacros": ["mpeinit"]会让webout不再被忽略,而"bannedStrings": []则不会报告任何内容,也不会回退到默认值。请重新列出你仍想保留的默认项。 noUnusedMacros配合格式化程序会移除@li依赖项。 这些条目会被编译器使用,因此,如果某个宏仅通过动态%&name调用间接引用,或由%include引入的程序提供,就会失去其声明,导致作业无法汇编。遇到这种情况时,请在项目或文件级别关闭此规则。- 即使文件没有调用某个宏,头部条目也可能是构建所必需的,因此删除前请先验证。 编译器还会内联另一个已声明宏的
<h4>所要求的内容,因此某个条目可能只是该调用链中的重复项(可安全删除,且生成的程序不会改变),也可能是该宏被引入文件的唯一途径(删除后会导致构建失败)。验证方法是编译并 grep 生成的文件:编辑前后分别运行sasjs compile,然后运行grep -c "%macro <name>" sasjsbuild/services/<path>.sas。 noUndeclaredMacros不会识别SASAUTOS。 SAS 自带的宏始终会被视为已声明(列表由@sasjs/sas-language生成),但通过SASAUTOS提供的宏对代码检查器不可见,因此会被报告。请在头部声明这些宏,或关闭此规则。noBannedStrings会报告注释或头部说明中的词语,而不仅仅是代码中的词语。 文件看起来没有问题,仍可能触发此规则;[line, column]指向该词语本身。bannedStrings下介绍了三种处理方式:改写、使用%str(WARN)ING,或针对 doxygen 标签设置单文件覆盖。noUnusedLibnames的判定范围有意设置得较宽。 只要 libref 出现在LIBNAME语句之外的任何位置,就会被视为已使用,包括双引号字符串中的出现,例如pathname("outData", "L")。由于单个文件并非完整作业,且此规则没有格式化修复,因此默认关闭。- 只有对于默认值此前按条件应用的设置,空的
.sasjslint才与没有.sasjslint不同。 从 4.0.0 开始,未设置的maxLineLength或hasMacroNameInMend会回退到文档规定的默认值(分别为 80 和true)。因此,若项目之前依靠省略这些设置来关闭相应规则,现在必须设置"maxLineLength": 0或"hasMacroNameInMend": false。 allowedGremlins中的条目是字符串,而不是数字:"0x0080",必须恰好包含四位十六进制数字。- CLI 输出为框线表格。 该输出供终端使用;不要将其粘贴到纯文本或仅支持 ASCII 的文档中,否则边框字符可能会造成问题。
- 不带子命令的
sasjs lint等同于sasjs lint find,会检查项目中的所有.sas文件。
验证
- 确认规则是否触发:将违规构造放入
.sas文件,然后运行terminal(command="npx sasjs lint")。命中时会显示一行消息以及[line, column]位置;没有显示行表示该规则已关闭或该构造被排除(先检查配置,再检查规则的排除项)。 - 确认配置更改是否生效:编辑
.sasjslint前后分别运行npx sasjs lint,并比较显示的行。默认关闭的规则需要设为true,而不是false。 - 确认格式化程序的效果:运行
npx sasjs lint fix并检查差异——它会改写<h4> SAS Macros </h4>部分和%mend行。 - 确认单文件覆盖设置:添加
@sasjslint块,运行sasjs lint,并检查被关闭的规则是否不再针对该文件报告,同时仍针对相邻文件报告。 - 确认禁用字符串:在文件中加入
%put WARNING: test;并运行sasjs lint——该行对应的位置会出现一条报告。将其改写为%put %str(WARN)ING: test;后,报告会消失,而日志行仍显示WARNING: test。 - 确认
ignoredMacros:对调用了未声明宏的文件运行 lint,将该宏的名称加入项目的.sasjslint,报告便会消失。然后从同一列表中移除webout,并确认在调用它的服务中%webout开始触发报告——该列表会替换默认列表,而不是在其基础上扩展。