sasjs-lint

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

@sasjs/lint

@sasjs/lint

<!-- SPDX-License-Identifier: MIT -->
@sasjs/lint
is the linting and formatting engine for SAS code. It is a TypeScript library with no SAS dependency - it reads
.sas
files as text - and it is the single engine behind
sasjs lint
, the SASjs VS Code extension's Problems panel, and the SASjs Server Studio editor.
This skill covers the rules, the
.sasjslint
file, the formatter, and the ways to invoke it. It does not cover the SAS language itself (see
sas
) or the rest of the CLI (see
sasjs-cli
).
<!-- SPDX-License-Identifier: MIT -->
@sasjs/lint
是 SAS 代码的 Lint 和格式化引擎。它是一个不依赖 SAS 的 TypeScript library——以文本方式读取
.sas
文件——并且是
sasjs lint
、SASjs VS Code extension 的 Problems panel 以及 SASjs Server Studio editor 背后的唯一引擎。
本 skill 介绍规则、
.sasjslint
文件、formatter 以及调用方式,不涵盖 SAS language 本身(参见
sas
),也不涵盖 CLI 的其他部分(参见
sasjs-cli
)。

When to use

何时使用

  • Writing or changing a
    .sasjslint
    file
  • A
    sasjs lint
    run reports a warning and you need to know what it means or how to silence it
  • Adding lint to a CI pipeline or a git pre-commit hook
  • Running the formatter (
    sasjs lint fix
    , format-on-save,
    formatText
    )
  • Explaining why a rule does or does not fire in an editor
  • 编写或修改
    .sasjslint
    文件
  • 运行
    sasjs lint
    后出现警告,需要了解其含义或如何忽略该警告
  • 将 Lint 添加到 CI pipeline 或 git pre-commit hook
  • 运行 formatter(
    sasjs lint fix
    、format-on-save、
    formatText
    )
  • 解释某条规则在 editor 中是否会触发以及为什么

Where it runs

运行位置

SurfaceHow it is invokedNotes
SASjs CLI
sasjs lint
,
sasjs lint find
,
sasjs lint fix
,
sasjs lint init
find
is the default subcommand.
init
writes a
.sasjslint
at the project root holding the full default config
VS Code extension (
sasjs/sasjs-for-vscode
, published on Open VSX)
Problems panel (ctrl+shift+M), Format Document, format-on-saveConfig precedence: project
.sasjslint
> setting
sasjs-for-vscode.lintConfig
>
~/.sasjslint
> defaults
SASjs Server Studio
POST /SASjsApi/code/lint
and
POST /SASjsApi/code/format
, body
{ code, filePath }
Rules come from the nearest
.sasjslint
at or above the file's folder on the SASjs Drive, walking up to the drive root. A drive-root
.sasjslint
is seeded on first start and shows in the file tree as
/.sasjslint
Library
lintText
,
lintFile
,
formatText
,
formatFile
, and the folder/project variants
See the API section below
All four call the same rules. The CLI, extension and server each pin a version of
@sasjs/lint
, so a rule added to the library only appears in a surface once that surface bumps its dependency - check the pinned version before telling a user a rule should fire in their editor.
使用场景调用方式说明
SASjs CLI
sasjs lint
、
sasjs lint find
、
sasjs lint fix
、
sasjs lint init
find
是默认 subcommand。
init
会在项目根目录写入一个包含完整默认配置的
.sasjslint
VS Code extension(
sasjs/sasjs-for-vscode
,发布于 Open VSX)
Problems panel(ctrl+shift+M)、Format Document、format-on-save配置优先级:项目
.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
Library
lintText
、
lintFile
、
formatText
、
formatFile
以及对应的 folder/project 变体
参见下方 API 部分
四种调用方式均使用同一套规则。CLI、extension 和 server 各自固定一个
@sasjs/lint
版本,因此 library 中新增的规则只有在该入口升级依赖后才会出现。在告知用户某条规则应该在其 editor 中触发之前,请先检查固定的版本。

Config resolution

配置解析

When no configuration is passed in, the library resolves it in this order:
  1. .sasjslint
    at the project root (the library walks up to find the project)
  2. .sasjslint
    in the user's home directory
  3. DefaultLintConfiguration
    - the built-in defaults
A setting that is absent takes its default, and so does a project with no
.sasjslint
at all.
DefaultLintConfiguration
is exported from
@sasjs/lint/utils/getLintConfig
, and it is what
sasjs lint init
writes out.
未传入 configuration 时,library 按以下顺序解析配置:
  1. 项目根目录中的
    .sasjslint
    (library 会向上查找以确定项目)
  2. 用户主目录中的
    .sasjslint
  3. DefaultLintConfiguration
    ,即内置默认配置
缺失的设置会采用默认值;完全没有
.sasjslint
的项目也是如此。
DefaultLintConfiguration
从
@sasjs/lint/utils/getLintConfig
导出,同时也是
sasjs lint init
写入的内容。

The .sasjslint file

.sasjslint
文件

json
{
  "noTrailingSpaces": true,
  "maxLineLength": 100,
  "noSingleAsteriskComments": true,
  "severityLevel": {
    "hasDoxygenHeader": "warn",
    "noEncodedPasswords": "error"
  }
}
json
{
  "noTrailingSpaces": true,
  "maxLineLength": 100,
  "noSingleAsteriskComments": true,
  "severityLevel": {
    "hasDoxygenHeader": "warn",
    "noEncodedPasswords": "error"
  }
}

Rules and defaults

规则与默认值

Two groups, and the way you switch a rule off depends on which group it is in.
On by default - switch off with
false
(except where noted):
SettingDefaultSeverityWhat it checks
hasDoxygenHeader
trueWARNINGthe file begins with a
/**
Doxygen header
hasMacroNameInMend
trueWARNING
%mend
carries the macro name
hasMacroParentheses
trueWARNINGmacros are defined with parentheses, and no space before the
(
noNestedMacros
trueWARNINGno
%macro
defined inside another macro
strictMacroDefinition
trueWARNINGno space in a parameter name (
%macro m(my var);
), no unrecognised option (
%macro m()/nonsense;
)
noUndeclaredMacros
trueWARNINGevery macro a file calls is declared in the file or its header
noUnusedMacros
trueWARNINGevery macro in
<h4> SAS Macros </h4>
is actually called
noTrailingSpaces
trueWARNINGno line ends in whitespace
noTabs
trueWARNINGno tab characters. Alias:
noTabIndentation
(deprecated)
noGremlins
trueWARNINGno zero-width or otherwise non-standard characters
indentationMultiple
2WARNINGleading spaces divide cleanly by this number
lowerCaseFileNames
trueWARNINGfilenames are lowercase (autocall on
*nix
requires it)
noSpacesInFileNames
trueWARNINGfilenames contain no spaces
noEncodedPasswords
trueERRORno
{sas00X}
or
{sasenc}
encoded password
maxLineLength
80WARNINGline length. Off with
0
Off by default - switch on with
true
:
SettingDefaultSeverityWhat it checks
noSingleAsteriskComments
falseWARNINGcomment statements of the form
* text;
noUnusedLibnames
falseWARNINGa libref the file assigns and never uses
hasRequiredMacroOptions
falseWARNINGmacros carry the options in
requiredMacroOptions
lineEndings
"off"
WARNINGline endings conform to
lf
or
crlf
Supporting settings (not rules):
SettingDefaultPurpose
ignoreList
[]
paths to skip. Files in
.gitignore
are skipped too
allowedGremlins
[]
4-hex-digit strings such as
"0x0080"
defaultHeader
see belowheader template applied to a file with no
/**
header
severityLevel
{}
per-rule override of
"warn"
/
"error"
maxHeaderLineLength
80line limit inside the header
maxDataLineLength
80line limit inside
datalines
/
cards
/
parmcards
records
requiredMacroOptions
[]
options required by
hasRequiredMacroOptions
ignoredLibnames
[]
librefs
noUnusedLibnames
leaves alone
规则分为两组,关闭某条规则的方式取决于它属于哪一组。
默认开启——除另有说明外,使用
false
关闭
:
设置默认值严重程度检查内容
hasDoxygenHeader
trueWARNING文件以
/**
Doxygen header 开头
hasMacroNameInMend
trueWARNING
%mend
包含 macro 名称
hasMacroParentheses
trueWARNINGmacro 定义时带括号,且
(
前没有空格
noNestedMacros
trueWARNING不在另一个 macro 内定义
%macro
strictMacroDefinition
trueWARNING参数名中没有空格(
%macro m(my var);
),且没有无法识别的 option(
%macro m()/nonsense;
)
noUndeclaredMacros
trueWARNING文件调用的每个 macro 都在文件或其 header 中声明
noUnusedMacros
trueWARNING
<h4> SAS Macros </h4>
中的每个 macro 都确实被调用
noTrailingSpaces
trueWARNING没有以空白字符结尾的行
noTabs
trueWARNING不包含 tab 字符。别名:
noTabIndentation
(已弃用)
noGremlins
trueWARNING不包含 zero-width 或其他非标准字符
indentationMultiple
2WARNING前导空格数可被该数字整除
lowerCaseFileNames
trueWARNING文件名使用小写(
*nix
上的 autocall 要求如此)
noSpacesInFileNames
trueWARNING文件名不包含空格
noEncodedPasswords
trueERROR不包含
{sas00X}
或
{sasenc}
编码密码
maxLineLength
80WARNING行长度。使用
0
关闭
默认关闭——使用
true
开启
:
设置默认值严重程度检查内容
noSingleAsteriskComments
falseWARNING
* text;
形式的 comment 语句
noUnusedLibnames
falseWARNING文件赋值后从未使用的 libref
hasRequiredMacroOptions
falseWARNINGmacro 包含
requiredMacroOptions
中要求的 options
lineEndings
"off"
WARNINGline ending 符合
lf
或
crlf
支持性设置(并非规则):
设置默认值用途
ignoreList
[]
要跳过的路径。
.gitignore
中的文件也会被跳过
allowedGremlins
[]
4 位十六进制字符串,例如
"0x0080"
defaultHeader
见下文应用于没有
/**
header 的文件的 header 模板
severityLevel
{}
按规则名称覆盖
"warn"
/
"error"
maxHeaderLineLength
80header 内的行长度限制
maxDataLineLength
80
datalines
/
cards
/
parmcards
records 内的行长度限制
requiredMacroOptions
[]
hasRequiredMacroOptions
要求的 options
ignoredLibnames
[]
noUnusedLibnames
不处理的 librefs

Severity and exit codes

严重程度与退出码

Every rule has three states: off, WARNING, or ERROR.
sasjs lint
returns a non-zero exit code only when an ERROR is reported - warnings leave the exit code at 0, so a hook or pipeline keyed on the exit code passes a file that only warns.
noEncodedPasswords
is the only rule that ships as ERROR.
severityLevel
promotes or demotes a rule by name, and accepts only
"warn"
and
"error"
; any other value is ignored without complaint:
json
{
  "severityLevel": {
    "maxLineLength": "error",
    "hasDoxygenHeader": "warn"
  }
}
每条规则都有三种状态:off、WARNING 或 ERROR。
sasjs lint
仅在报告 ERROR 时返回非零退出码——warning 会使退出码保持为 0,因此以退出码为依据的 hook 或 pipeline 会放行仅产生 warning 的文件。
noEncodedPasswords
是唯一内置为 ERROR 的规则。
severityLevel
可按名称提升或降低规则的严重程度,并且只接受
"warn"
和
"error"
;其他值会被忽略,不会产生提示:
json
{
  "severityLevel": {
    "maxLineLength": "error",
    "hasDoxygenHeader": "warn"
  }
}

Per-file overrides

按文件覆盖配置

A file adjusts the rules that apply to itself with a
@sasjslint
block in its Doxygen header:
sas
/**
  @file
  @brief Calls the settlement API

  @sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}

  <h4> SAS Macros </h4>
  @li mf_trim.sas

**/
  • Scalars replace, arrays are additive (
    allowedGremlins
    ,
    requiredMacroOptions
    ,
    ignoredLibnames
    gain entries rather than replacing), objects merge key by key (
    severityLevel
    ).
  • The override applies to the formatter as well, so
    sasjs lint fix
    honours it - a file that switches
    noUnusedMacros
    off keeps the header entries it would otherwise lose.
  • Only the header is read, and only a line that starts with the tag followed immediately by
    {
    . A malformed or unterminated block is ignored rather than raised.
  • The header wins over the configuration the caller passes.
  • ignoreList
    is the one setting a header cannot use: it is evaluated before the file is read.
  • Place the block outside the
    <h4> SAS Macros </h4>
    section, which the formatter rewrites.
文件可以通过 Doxygen header 中的
@sasjslint
block 调整适用于自身的规则:
sas
/**
  @file
  @brief Calls the settlement API

  @sasjslint {"maxHeaderLineLength": 200, "noUndeclaredMacros": false}

  <h4> SAS Macros </h4>
  @li mf_trim.sas

**/
  • 标量值会被替换,数组会累加(
    allowedGremlins
    、
    requiredMacroOptions
    、
    ignoredLibnames
    会增加条目,而不是替换原值),对象则按 key 逐项合并(
    severityLevel
    )。
  • 覆盖项也适用于 formatter,因此
    sasjs lint fix
    会遵循该配置——关闭
    noUnusedMacros
    的文件会保留原本会被移除的 header 条目。
  • 仅读取 header,并且只识别以 tag 开头且紧接着
    {
    的行。格式错误或未终止的 block 会被忽略,而不会抛出错误。
  • Header 的优先级高于调用方传入的配置。
  • ignoreList
    是 header 无法使用的唯一设置,因为它会在读取文件之前完成求值。
  • 请将 block 放在
    <h4> SAS Macros </h4>
    section 之外,因为 formatter 会重写该 section。

The formatter

Formatter

The formatter applies the fixable rules on save. Implemented today:
  • add the macro name to
    %mend
  • add a Doxygen header template when the file has none
  • add macros a file uses to
    <h4> SAS Macros </h4>
    , de-duplicated and sorted
  • remove macros from that section that the file does not use
  • remove trailing spaces
Not implemented (do not promise these): tabs to spaces, gremlin removal, line-ending correction, converting single-asterisk comments to block comments.
defaultHeader
uses
{lineEnding}
as the newline placeholder - a literal
\n
does not work:
json
{
  "defaultHeader": "/**{lineEnding}  @file{lineEnding}  @brief Our Company Brief{lineEnding}**/"
}
Formatter 会在保存时应用可修复的规则。目前已实现:
  • 为
    %mend
    添加 macro 名称
  • 当文件没有 Doxygen header 时,添加 Doxygen header 模板
  • 将文件使用的 macros 添加到
    <h4> SAS Macros </h4>
    ,并去重和排序
  • 从该 section 中移除文件未使用的 macros
  • 移除行尾空格
尚未实现(不要承诺支持这些功能):将 tab 转换为空格、移除 gremlin、修正 line ending、将 single-asterisk comments 转换为 block comments。
defaultHeader
使用
{lineEnding}
作为换行占位符——不能使用字面量
\n
:
json
{
  "defaultHeader": "/**{lineEnding}  @file{lineEnding}  @brief Our Company Brief{lineEnding}**/"
}

Library API

Library API

ts
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
    ,
    lintProject
  • formatText(text, configuration?)
    ,
    formatFile
    ,
    formatFolder
    ,
    formatProject
  • new LintConfig(json)
    builds a config from a plain object;
    config.override(json)
    returns a new config with an override merged in
  • Diagnostic
    carries
    message
    ,
    lineNumber
    ,
    startColumnNumber
    ,
    endColumnNumber
    and
    severity
  • Severity
    is
    Info
    (0),
    Warning
    (1),
    Error
    (2)
Passing an explicit
LintConfig
matters when the caller knows the rules - a server or editor linting a buffer with no path on disk cannot rely on the working-directory walk.
ts
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
    、
    lintProject
  • formatText(text, configuration?)
    、
    formatFile
    、
    formatFolder
    、
    formatProject
  • new LintConfig(json)
    根据普通对象构建配置;
    config.override(json)
    返回合并了 override 的新配置
  • Diagnostic
    包含
    message
    、
    lineNumber
    、
    startColumnNumber
    、
    endColumnNumber
    和
    severity
  • Severity
    包括
    Info
    (0)、
    Warning
    (1)、
    Error
    (2)
当调用方了解相关规则时,传入显式的
LintConfig
很重要——server 或 editor 对磁盘上没有 path 的 buffer 进行 Lint 时,无法依赖 working directory 查找配置。

Pitfalls

常见陷阱

  • Off means something different per rule. The on-by-default rules switch off with
    false
    ;
    maxLineLength
    switches off with
    0
    ;
    lineEndings
    switches off with
    "off"
    ; the off-by-default rules ignore
    false
    (it is the same as leaving them out) and need
    true
    . Getting this wrong is the most common reason a rule "does not work".
  • maxHeaderLineLength
    and
    maxDataLineLength
    are inert when
    maxLineLength
    is off
    , because they are scoped to it. The effective limit is the higher of the two values, so setting one lower than
    maxLineLength
    is silently ignored.
  • noUnusedMacros
    plus the formatter removes
    @li
    dependencies.
    Those entries are consumed by the compiler, so a macro referenced only indirectly - through a dynamic
    %&name
    call, or supplied by an
    %include
    d program - loses its declaration and the job can fail to assemble. Switch the rule off for the project or the file when that applies.
  • noUndeclaredMacros
    does not see
    SASAUTOS
    .
    Macros that ship with SAS are always treated as declared (the list is generated from
    @sasjs/sas-language
    ), but macros made available through
    SASAUTOS
    are invisible to the linter and are reported. Declare them in the header or switch the rule off.
  • noUnusedLibnames
    is deliberately generous.
    A libref counts as used if it appears anywhere outside a
    LIBNAME
    statement - including inside a double-quoted string, as in
    pathname("outData", "L")
    . It is off by default because a file is not a complete job, and it has no formatter fix.
  • An empty
    .sasjslint
    is not the same as no
    .sasjslint
    only for settings whose default was previously applied conditionally. Since 4.0.0 an absent
    maxLineLength
    or
    hasMacroNameInMend
    falls back to its documented default (80, and
    true
    ), so a project that relied on omitting them to disable those rules must now set
    "maxLineLength": 0
    or
    "hasMacroNameInMend": false
    .
  • allowedGremlins
    entries are strings, not numbers
    :
    "0x0080"
    , exactly four hex digits.
  • The CLI output is a box-drawn table. It is meant for a terminal; do not paste it into a plain-text or ASCII-only document without expecting the border glyphs to be a problem.
  • sasjs lint
    with no subcommand behaves as
    sasjs lint find
    , linting every
    .sas
    file in the project.
  • Off 对不同规则的含义不同。 默认开启的规则使用
    false
    关闭;
    maxLineLength
    使用
    0
    关闭;
    lineEndings
    使用
    "off"
    关闭;默认关闭的规则会忽略
    false
    (等同于不配置),必须使用
    true
    开启。配置错误是规则“不起作用”的最常见原因。
  • 当
    maxLineLength
    关闭时,
    maxHeaderLineLength
    和
    maxDataLineLength
    不会生效
    ,因为它们的作用域限定于
    maxLineLength
    。实际限制取两者中的较高值,因此将其中一个值设置得低于
    maxLineLength
    会被静默忽略。
  • 启用
    noUnusedMacros
    后,formatter 会移除
    @li
    依赖项。
    这些条目供 compiler 使用,因此仅通过 dynamic
    %&name
    call 间接引用,或由
    %include
    d program 提供的 macro 会失去声明,job 可能无法 assemble。遇到这种情况时,请对项目或文件关闭该规则。
  • noUndeclaredMacros
    无法识别
    SASAUTOS
    。
    SAS 随附的 macros 始终被视为已声明(该列表根据
    @sasjs/sas-language
    生成),但通过
    SASAUTOS
    提供的 macros 对 linter 不可见,因此会被报告。可以在 header 中声明它们,或关闭该规则。
  • noUnusedLibnames
    有意采用较宽松的判断。
    只要 libref 出现在
    LIBNAME
    statement 之外的任何位置,就会被视为已使用,包括双引号字符串中,例如
    pathname("outData", "L")
    。该规则默认关闭,因为单个文件并非完整 job,而且它没有对应的 formatter 修复。
  • 空的
    .sasjslint
    与没有
    .sasjslint
    并不相同
    ,这种差异仅适用于以前按条件应用默认值的设置。从 4.0.0 开始,缺失的
    maxLineLength
    或
    hasMacroNameInMend
    会回退到文档规定的默认值(分别为 80 和
    true
    ),因此过去依靠省略这些配置来禁用规则的项目,现在必须设置
    "maxLineLength": 0
    或
    "hasMacroNameInMend": false
    。
  • allowedGremlins
    的条目是字符串,不是数字
    :例如
    "0x0080"
    ,且必须恰好包含 4 位十六进制数字。
  • CLI 输出是带方框线条的表格。 它专为 terminal 设计;不要在未考虑边框字形问题的情况下直接粘贴到纯文本或仅支持 ASCII 的文档中。
  • 不带 subcommand 运行
    sasjs lint
    时,其行为与
    sasjs lint find
    相同
    ,会检查项目中的所有
    .sas
    文件。

Verification

验证

  • Confirm a rule fires: put the offending construct in a
    .sas
    file and run
    terminal(command="npx sasjs lint")
    . A hit prints a row with the message and a
    [line, column]
    position; no row means the rule is off or the construct is excluded (check the config first, then the rule's exclusions).
  • Confirm a config change took effect: run
    npx sasjs lint
    before and after editing
    .sasjslint
    , and compare the rows. A rule that is off by default needs
    true
    , not
    false
    .
  • Confirm the formatter's effect: run
    npx sasjs lint fix
    and inspect the diff - the
    <h4> SAS Macros </h4>
    section and
    %mend
    lines are the ones it rewrites.
  • Confirm a per-file override: add the
    @sasjslint
    block, run
    sasjs lint
    , and check that the rule it switches off no longer reports for that file while still reporting for its neighbours.
  • 确认规则是否触发:将存在问题的代码放入
    .sas
    文件,并运行
    terminal(command="npx sasjs lint")
    。如果命中,会输出一行消息及其
    [line, column]
    位置;如果没有输出,则说明规则已关闭或该代码被排除(先检查配置,再检查规则的 exclusions)。
  • 确认配置变更是否生效:在编辑
    .sasjslint
    前后分别运行
    npx sasjs lint
    ,并比较输出结果。默认关闭的规则需要设置为
    true
    ,而不是
    false
    。
  • 确认 formatter 的效果:运行
    npx sasjs lint fix
    并检查 diff——
    <h4> SAS Macros </h4>
    section 和
    %mend
    行是它会重写的内容。
  • 确认按文件覆盖配置:添加
    @sasjslint
    block,运行
    sasjs lint
    ,并检查该规则关闭后不再为该文件报告问题,同时仍会为相邻文件报告。