hana-linter
v1.4.0
Published
Linter for SAP HANA artifacts in an SAP CAP project
Readme
hana-linter
Naming-convention lint for SAP HANA artifacts in CAP projects.
⚠️ Work in progress. This project is under active development. APIs, configuration options, and supported artifact types may change between releases. See Parser Status for the current state of content extraction support.
Lint SAP HANA artifact file names and content identifiers in CAP projects using configurable regex-based naming rules.
Why
Teams often rely on naming conventions for HANA artifacts such as tables and views. When these conventions are enforced manually, drift is common and code reviews become noisy.
hana-linter helps you:
- enforce naming standards consistently
- catch violations early in local development and CI
- apply different rules per file extension
- keep naming policy in version control via a single config file
How It Works
hana-linter reads a .hana-linter.json file and validates artifact file names against rules grouped by extension.
It supports two lint modes:
- Full scan mode: no file arguments, recursively scans
rootDir - File-list mode: pass file paths, only those files are validated
Rule groups per extension:
groups.all: every rule must match (AND)groups.any: at least one rule must match (OR)
You can define extension: "*" as a shared rule set. Its rules are applied to every file extension and are merged with any extension-specific rule set.
Content-based linting uses purpose-built parsers to reliably extract identifiers from HANA artifact files. SQL DDL artifact types (.hdbtable, .hdbview, .hdbprocedure, .hdbfunction, .hdbtabletype, .hdbrole, .hdbsequence, .hdbindex, .hdbtrigger) use Chevrotain-powered lexers and CST parsers that correctly handle block and line comments, multi-line definitions, quoted identifiers, and HANA-specific DDL constructs. The JSON-like .hdbschedulerjob format is also parsed with Chevrotain, tolerating C-style comments (// and /* … */) and trailing commas that standard JSON.parse() rejects. The XML-based .hdbcalculationview format uses fast-xml-parser to navigate the logical model tree — without the false positives and false negatives that ad-hoc regex scanning produces.
Parser Status
Work in progress. Not all artifact types have been migrated to the Chevrotain-based parsing infrastructure yet.
| Artifact extension | Content extractor | Status |
| ------------------------- | ---------------------- | ---------------------- |
| .hdbtable | Chevrotain lexer + CST | ✅ Migrated |
| .hdbview | Chevrotain lexer + CST | ✅ Migrated |
| .hdbprocedure | Chevrotain lexer + CST | ✅ Migrated |
| .hdbfunction | Chevrotain lexer + CST | ✅ Migrated |
| .hdbtabletype | Chevrotain lexer + CST | ✅ Migrated |
| .hdbrole | Chevrotain lexer + CST | ✅ Migrated |
| .hdbcalculationview | fast-xml-parser (XML) | ✅ Migrated |
| .hdbanalyticalprivilege | — | ❌ Not yet implemented |
| .hdbsequence | Chevrotain lexer + CST | ✅ Migrated |
| .hdbconstraint | — | ❌ Not yet implemented |
| .hdbschedulerjob | Chevrotain lexer + CST | ✅ Migrated |
| .hdbindex | Chevrotain lexer + CST | ✅ Migrated |
| .hdbtrigger | Chevrotain lexer + CST | ✅ Migrated |
- The regex file name validations for all of the above listed extensions are implemented.
- Not implemented:
contentRuleSetstargeting.hdbanalyticalprivilegeor.hdbconstraintwill silently return no results — no identifiers are extracted and no content issues are raised.
Install
Local (recommended for projects)
npm install --save-dev hana-linterRun with:
npx hana-linterGlobal
npm install -g hana-linterQuick Start
- Generate a default config in your project root:
hana-linter init- Run the linter:
hana-linter- If needed, regenerate and overwrite config:
hana-linter init --forceCommands
hana-linter
Run lint using .hana-linter.json from the current working directory.
hana-linterLint specific files only:
hana-linter db/src/T_CUSTOMER.hdbtable db/src/V_ACTIVE_USERS.hdbviewUse a custom config path:
hana-linter --config ./config/.hana-linter.jsonhana-linter init
Create .hana-linter.json in the current working directory from the bundled default template.
hana-linter initOverwrite existing config:
hana-linter init --forceConfiguration
Create a .hana-linter.json file in your project root.
Configuration Fields
rootDir(string): directory to scan in full scan modeignoredDirectories(string[]): folder names ignored during recursive traversalextensionRuleSets(array): rule definitions grouped by file extensioncontentRuleSets(optional array): naming rules for identifiers extracted from file contents (for example table fields and procedure/function parameters)
Each extensionRuleSets item contains:
extension(string): target extension, for example.hdbtable; use*to target all extensionsfolderName(optional string): enforce that matching files are located in a folder with this name (at any depth underrootDir)groups.all(optional array): all rules must matchgroups.any(optional array): at least one rule must match
Each rule contains:
description(string): readable rule label for outputpattern(string): regex source (without/delimiters)flags(optional string): regex flags, for examplei,u,iu
At least one of groups.all or groups.any must be present for each extension.
When folderName is omitted, no folder-location enforcement is applied.
Each contentRuleSets item contains:
extension(string): target extension, for example.hdbtable; use*to target all extensionstarget(string): extracted identifier type to validate; one offield,inputParameter,outputParameter,roleName,grantedRoleName,sequenceName,jobAction,indexName,triggerNamegroups.all(optional array): all rules must matchgroups.any(optional array): at least one rule must match
Supported extractors in this version:
| target | Supported extensions | Extracted identifiers |
| ----------------- | --------------------- | ----------------------------------------------------------------------------- |
| field | .hdbtable | Column names |
| field | .hdbview | Column aliases (explicit list or AS aliases) |
| field | .hdbtabletype | Column names |
| inputParameter | .hdbprocedure | IN and INOUT parameters |
| inputParameter | .hdbfunction | IN parameters (functions accept IN only) |
| outputParameter | .hdbprocedure | OUT and INOUT parameters |
| roleName | .hdbrole | The role name defined in the file |
| grantedRoleName | .hdbrole | Each role listed in extends roles { ... } |
| field | .hdbcalculationview | Output attributes, calculated attributes, base/calculated/restricted measures |
| inputParameter | .hdbcalculationview | Input parameters (variable[@parameter="true"]) |
| sequenceName | .hdbsequence | The sequence name declared in the file |
| jobAction | .hdbschedulerjob | The procedure/function reference in the action field |
| indexName | .hdbindex | The index name declared in the file |
| triggerName | .hdbtrigger | The trigger name declared in the file |
Default Config Example
{
"rootDir": "db",
"ignoredDirectories": ["node_modules", ".git", "gen"],
"extensionRuleSets": [
{
"extension": "*",
"groups": {
"all": [
{
"description": "Upper snake case only",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
},
{
"description": "Max length 30",
"pattern": "^.{1,30}$",
"flags": "u"
}
]
}
},
{
"extension": ".hdbtable",
"folderName": "tables",
"groups": {
"any": [
{
"description": "Prefix T_",
"pattern": "^T_.+"
},
{
"description": "Prefix TX_",
"pattern": "^TX_.+"
}
]
}
},
{
"extension": ".hdbview",
"groups": {
"all": [
{
"description": "Starts with V_",
"pattern": "^V_.+"
}
]
}
}
],
"contentRuleSets": [
{
"extension": ".hdbtable",
"target": "field",
"groups": {
"all": [
{
"description": "Field names in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbprocedure",
"target": "inputParameter",
"groups": {
"all": [
{
"description": "Input parameters prefixed with IP_",
"pattern": "^IP_[A-Z0-9_]+$"
}
]
}
},
{
"extension": ".hdbprocedure",
"target": "outputParameter",
"groups": {
"all": [
{
"description": "Output parameters prefixed with OP_",
"pattern": "^OP_[A-Z0-9_]+$"
}
]
}
},
{
"extension": ".hdbview",
"target": "field",
"groups": {
"all": [
{
"description": "View column aliases in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbfunction",
"target": "inputParameter",
"groups": {
"all": [
{
"description": "Input parameters prefixed with IP_",
"pattern": "^IP_[A-Z0-9_]+$"
}
]
}
},
{
"extension": ".hdbtabletype",
"target": "field",
"groups": {
"all": [
{
"description": "Field names in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbrole",
"target": "roleName",
"groups": {
"all": [
{
"description": "Role names prefixed with R_",
"pattern": "^R_.+"
}
]
}
},
{
"extension": ".hdbcalculationview",
"target": "field",
"groups": {
"all": [
{
"description": "Output column IDs in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbcalculationview",
"target": "inputParameter",
"groups": {
"all": [
{
"description": "Input parameters prefixed with IP_",
"pattern": "^IP_[A-Z0-9_]+$"
}
]
}
},
{
"extension": ".hdbsequence",
"target": "sequenceName",
"groups": {
"all": [
{
"description": "Sequence names in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbschedulerjob",
"target": "jobAction",
"groups": {
"all": [
{
"description": "Job action must reference a package-qualified procedure (contains ::)",
"pattern": "::"
}
]
}
},
{
"extension": ".hdbindex",
"target": "indexName",
"groups": {
"all": [
{
"description": "Index names in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
},
{
"extension": ".hdbtrigger",
"target": "triggerName",
"groups": {
"all": [
{
"description": "Trigger names in uppercase snake case",
"pattern": "^[A-Z0-9]+(?:_[A-Z0-9]+)*$"
}
]
}
}
]
}Exit Codes
0: lint passed orinitcompleted successfully1: lint violations found or command failed
This makes the CLI suitable for CI pipelines.
CI Example
name: lint-hana-names
on: [push, pull_request]
jobs:
hana-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx hana-linterPre-commit Hook (husky + lint-staged)
hana-linter's file-list mode (passing files as arguments) integrates cleanly with lint-staged: lint-staged appends each staged file that matches the glob to the command, so only the files you are about to commit are linted.
1. Install the tools
npm install --save-dev husky lint-staged
npx husky init2. Configure lint-staged
Add a lint-staged key to package.json:
{
"lint-staged": {
"*.hdb*": "hana-linter"
}
}lint-staged turns this into hana-linter <file1> <file2> … for every staged file that matches the glob, which is exactly the file-list mode hana-linter supports.
3. Wire the pre-commit hook
Replace the contents of .husky/pre-commit with:
npx lint-stagedNow, every git commit will automatically run hana-linter against the staged HANA artifact files. If any naming violations are found, the commit is blocked and the issues are reported in the terminal.
Tip: Add
hana-lintertodependencies(notdevDependencies) if the linter also runs in production CI, so it is available after a plainnpm ci --productioninstall. For pre-commit-only use,devDependenciesis fine.
Requirements
- Node.js >= 14
- npm >= 7
Contributing
Contributions are welcome. Please open an issue or submit a pull request.
License
MIT
