pbiplint 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # pbiplint
2
2
 
3
3
  Best-practice linter for Power BI semantic models. Point it at a `.SemanticModel` folder, a PBIP
4
- folder, or one `.tmdl` file and get ranked findings with a link to a fix page for each rule.
5
- Nothing is uploaded: it reads the files you name and writes to your terminal. Node 20 or later.
4
+ folder, a `definition` folder, or one `.tmdl` file and get ranked findings with a link to a fix
5
+ page for each rule. Nothing is uploaded: it reads the files you name and writes to your terminal.
6
+ Node 20 or later.
6
7
 
7
8
  ```bash
8
9
  npx pbiplint path/to/Model.SemanticModel
@@ -10,12 +11,15 @@ npx pbiplint --sample # a bundled model wi
10
11
  npx pbiplint path/to/model --format sarif --output pbiplint.sarif
11
12
  npx pbiplint path/to/model --format markdown
12
13
  npx pbiplint rules # every rule with status and severity
14
+ npx pbiplint --help # every option, in one screen
15
+ npx pbiplint --version
13
16
  ```
14
17
 
15
18
  Formats: `text` (default), `json`, `sarif` (for GitHub code scanning and editors), `markdown`.
16
19
 
17
- Exit codes: `0` no findings at or above `--fail-on` (default `error`), `1` findings, `2` usage or
18
- input error. `--fail-on warning` tightens the gate; `--fail-on none` always exits 0.
20
+ Exit codes: `0` no findings at or above `--fail-on`, `1` findings, `2` usage or input error.
21
+ `--fail-on error` is the default; `--fail-on warning` and `--fail-on info` tighten the gate;
22
+ `--fail-on none` always exits 0.
19
23
 
20
24
  ## Configuration
21
25
 
package/dist/pbiplint.mjs CHANGED
@@ -5,7 +5,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
5
5
  import { basename as basename2, dirname as dirname4, relative as relative2, resolve as resolve3 } from "node:path";
6
6
 
7
7
  // ../core/src/version.ts
8
- var VERSION = "0.1.1";
8
+ var VERSION = "0.1.2";
9
9
 
10
10
  // ../core/src/engine/config.ts
11
11
  var ConfigError = class extends Error {
@@ -36,6 +36,8 @@ function resolveConfig(raw = {}) {
36
36
  for (const k of Object.keys(raw))
37
37
  if (k !== "rules" && k !== "failOn" && k !== "$schema")
38
38
  throw new ConfigError(`pbiplint.config.json: unknown key "${k}"`);
39
+ if (raw.$schema !== void 0 && typeof raw.$schema !== "string")
40
+ throw new ConfigError('pbiplint.config.json: "$schema" must be a string');
39
41
  const out = { disabled: /* @__PURE__ */ new Set(), severity: /* @__PURE__ */ new Map(), failOn: 3 };
40
42
  if (raw.rules !== void 0) {
41
43
  if (!isRecord(raw.rules))
@@ -2320,35 +2322,31 @@ function parseTmdl(file, text) {
2320
2322
  const roots = [];
2321
2323
  const issues = [];
2322
2324
  const stack = [];
2323
- let pendingDescription = [];
2324
- let descriptionLine = 0;
2325
- let descriptionText = "";
2326
- const orphanDescription = () => {
2325
+ let pendingDescription = null;
2326
+ const orphanDescription = (pending) => {
2327
2327
  issues.push({
2328
2328
  file,
2329
- line: descriptionLine,
2330
- text: descriptionText,
2329
+ line: pending.line,
2330
+ text: pending.text,
2331
2331
  reason: "description is not followed by a declaration"
2332
2332
  });
2333
- pendingDescription = [];
2333
+ pendingDescription = null;
2334
2334
  };
2335
2335
  let i = 0;
2336
2336
  while (i < lines.length) {
2337
2337
  const raw = lines[i];
2338
2338
  const lineNo = i + 1;
2339
2339
  if (raw.trim() === "") {
2340
- if (pendingDescription.length) orphanDescription();
2340
+ if (pendingDescription) orphanDescription(pendingDescription);
2341
2341
  i++;
2342
2342
  continue;
2343
2343
  }
2344
2344
  const indent = tabIndent(raw);
2345
2345
  const content = raw.slice(indent);
2346
2346
  if (content.startsWith("///")) {
2347
- if (!pendingDescription.length) {
2348
- descriptionLine = lineNo;
2349
- descriptionText = raw;
2350
- }
2351
- pendingDescription.push(content.replace(/^\/\/\/ ?/, ""));
2347
+ const line = content.replace(/^\/\/\/ ?/, "");
2348
+ if (pendingDescription) pendingDescription.lines.push(line);
2349
+ else pendingDescription = { line: lineNo, text: raw, lines: [line] };
2352
2350
  i++;
2353
2351
  continue;
2354
2352
  }
@@ -2425,9 +2423,9 @@ function parseTmdl(file, text) {
2425
2423
  node = { ...base, kind: "flag", type: h.type.toLowerCase() };
2426
2424
  }
2427
2425
  }
2428
- if (pendingDescription.length) {
2429
- node.description = pendingDescription.join("\n");
2430
- pendingDescription = [];
2426
+ if (pendingDescription) {
2427
+ node.description = pendingDescription.lines.join("\n");
2428
+ pendingDescription = null;
2431
2429
  }
2432
2430
  stack.length = indent;
2433
2431
  const parent = indent > 0 ? stack[indent - 1] : void 0;
@@ -2446,7 +2444,7 @@ function parseTmdl(file, text) {
2446
2444
  stack[indent] = node;
2447
2445
  i++;
2448
2446
  }
2449
- if (pendingDescription.length) orphanDescription();
2447
+ if (pendingDescription) orphanDescription(pendingDescription);
2450
2448
  return { file, roots, issues, lineCount: lines.length };
2451
2449
  }
2452
2450
 
@@ -2492,7 +2490,7 @@ function rank(findings, rules, config) {
2492
2490
  g.findings.push(f);
2493
2491
  }
2494
2492
  return [...groups.values()].sort(
2495
- (a, b) => b.rule.severity - a.rule.severity || CATEGORY_ORDER.indexOf(a.rule.category) - CATEGORY_ORDER.indexOf(b.rule.category) || b.findings.length - a.findings.length || a.rule.id.localeCompare(b.rule.id)
2493
+ (a, b) => b.rule.severity - a.rule.severity || CATEGORY_ORDER.indexOf(a.rule.category) - CATEGORY_ORDER.indexOf(b.rule.category) || b.findings.length - a.findings.length || a.rule.id.localeCompare(b.rule.id, "en")
2496
2494
  );
2497
2495
  }
2498
2496
 
@@ -3142,7 +3140,7 @@ Read more: https://pbiplint.com/rules/large-tables-should-be-partitioned`,
3142
3140
  markdown: "### Why it matters\n\nThe description is the tooltip a report author sees when hovering a field in the field list, and it is the only place in the model to say what a measure counts, which currency a column is in, or which of two similar fields to use. Without it, every author works that out from the name, and gets it wrong at about the same rate. Descriptions also feed documentation tools, so the same sentence pays off twice.\n\n### How to fix it\n\nIn Power BI Desktop, open Model view, select the object, and type the Description in the Properties pane. In the TMDL file a description is one or more `///` lines directly above the object. With hundreds of objects, Tabular Editor can paste descriptions into many objects at once; the TMDL comment lines need no other tool.\n\n### Quirks\n\n- Visibility is the object's own isHidden flag: a visible column inside a hidden table is still reported.\n- A calculation group table is reported once, as a calculation group.\n\nRead more: https://pbiplint.com/rules/objects-with-no-description"
3143
3141
  },
3144
3142
  PARSE_ISSUE: {
3145
- text: "Why it matters\n\nThe parser skipped the line, so whatever it declared, a column, a property, a measure, is missing from the model the rules see. Findings on that object and on anything that references it may be missing or wrong, and a result that looks clean may not be. The orphaned description is the mild case: no declaration is lost, only the description, which stops at the blank line instead of reaching the object below it, so that object is read as having none. Tabular Editor's TMDL reader is stricter and refuses to open a file that puts a blank line after a /// line at all.\n\nHow to fix it\n\nOpen the file at the reported line. TMDL is indented with tabs, and expression blocks open and close with on their own lines. A /// description must sit directly above its declaration, with no blank line between them. Power BI Desktop writes valid TMDL, so a parse issue usually means a hand edit or a merge conflict marker.\n\nRead more: https://pbiplint.com/rules/parse-issue",
3143
+ text: "Why it matters\n\nThe parser skipped the line, so whatever it declared, a column, a property, a measure, is missing from the model the rules see. Findings on that object and on anything that references it may be missing or wrong, and a result that looks clean may not be. The orphaned description is the mild case: no declaration is lost, only the description, which stops at the blank line instead of reaching the object below it, so that object is read as having none. Tabular Editor's TMDL reader is stricter and refuses to open a file that puts a blank line after a /// line at all.\n\nHow to fix it\n\nOpen the file at the reported line. TMDL is indented with tabs, and expression blocks open and close with ``` on their own lines. A /// description must sit directly above its declaration, with no blank line between them. Power BI Desktop writes valid TMDL, so a parse issue usually means a hand edit or a merge conflict marker.\n\nRead more: https://pbiplint.com/rules/parse-issue",
3146
3144
  markdown: "### Why it matters\n\nThe parser skipped the line, so whatever it declared, a column, a property, a measure, is missing from the model the rules see. Findings on that object and on anything that references it may be missing or wrong, and a result that looks clean may not be. The orphaned description is the mild case: no declaration is lost, only the description, which stops at the blank line instead of reaching the object below it, so that object is read as having none. Tabular Editor's TMDL reader is stricter and refuses to open a file that puts a blank line after a `///` line at all.\n\n### How to fix it\n\nOpen the file at the reported line. TMDL is indented with tabs, and expression blocks open and close with ``` on their own lines. A `///` description must sit directly above its declaration, with no blank line between them. Power BI Desktop writes valid TMDL, so a parse issue usually means a hand edit or a merge conflict marker.\n\nRead more: https://pbiplint.com/rules/parse-issue"
3147
3145
  },
3148
3146
  PARTITION_NAME_SHOULD_MATCH_TABLE_NAME_FOR_SINGLE_PARTITION_TABLES: {
@@ -3249,7 +3247,7 @@ import { basename, dirname as dirname3, join as join3, relative, resolve as reso
3249
3247
  var toPosix = (p) => p.split("\\").join("/");
3250
3248
  function readTmdlFiles(root, dir, out) {
3251
3249
  for (const entry of readdirSync(dir, { withFileTypes: true }).sort(
3252
- (a, b) => a.name.localeCompare(b.name)
3250
+ (a, b) => a.name.localeCompare(b.name, "en")
3253
3251
  )) {
3254
3252
  const p = join3(dir, entry.name);
3255
3253
  if (entry.isDirectory()) readTmdlFiles(root, p, out);
@@ -3290,7 +3288,7 @@ function resolveModel(input) {
3290
3288
  }
3291
3289
 
3292
3290
  // src/main.ts
3293
- var VERSION2 = true ? "0.1.1" : "0.0.0-dev";
3291
+ var VERSION2 = true ? "0.1.2" : "0.0.0-dev";
3294
3292
  function listRules() {
3295
3293
  const width = Math.max(...defaultRules.map((r) => r.id.length));
3296
3294
  return defaultRules.map(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pbiplint",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Lint Power BI semantic models (TMDL) for best-practice violations, with text, JSON, SARIF, and Markdown output. Nothing is uploaded.",
5
5
  "keywords": [
6
6
  "power-bi",