@webpieces/eslint-rules 0.4.716 → 0.4.717

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
@@ -7,7 +7,7 @@ ESLint rules for WebPieces code patterns and architecture enforcement.
7
7
  - `catch-error-pattern` - Enforce toError() usage in catch blocks
8
8
  - `no-unmanaged-exceptions` - Discourage try-catch outside tests
9
9
  - `max-method-lines` - Enforce maximum method length (test-framework container callbacks such as `describe(...)` are exempt in `*.spec.ts` / `*.test.ts` — see below)
10
- - `max-file-lines` - Enforce maximum file length
10
+ - `max-file-lines` - Enforce maximum file length (machine-generated trees are exempt with no config; `allowedPaths` adds your own un-authored trees)
11
11
  - `enforce-architecture` - Enforce architecture dependency boundaries
12
12
  - `no-json-property-primitive-type` - Ban @JsonProperty({ type: String/Number/Boolean })
13
13
  - `require-typed-template` - Require [templateClassType] on ng-template with let- variables (Angular)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/eslint-rules",
3
- "version": "0.4.716",
3
+ "version": "0.4.717",
4
4
  "description": "ESLint rules for WebPieces code patterns and architecture enforcement",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,7 @@
22
22
  "directory": "packages/tooling/eslint-rules"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/rules-config": "0.4.716"
25
+ "@webpieces/rules-config": "0.4.717"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "eslint": ">=8.0.0"
@@ -5,7 +5,13 @@
5
5
  * Default: 700 lines
6
6
  *
7
7
  * Configuration:
8
- * '@webpieces/max-file-lines': ['error', { max: 700 }]
8
+ * '@webpieces/max-file-lines': ['error', { max: 700, allowedPaths: ['vendor-glob-here'] }]
9
+ *
10
+ * Machine-generated trees (the GENERATED_CODE_PATHS list in @webpieces/rules-config: __generated__
11
+ * and generated directories at any depth, .generated.ts / .generated.tsx files, and dist) are exempt
12
+ * with NO configuration, and `allowedPaths` ADDS your own un-authored trees to that floor — the same
13
+ * exemption the edit-time hook and the build-time validator apply, so one engine cannot pass a file
14
+ * the next one blocks. Never list hand-written code there; a long file you wrote gets refactored.
9
15
  */
10
16
  import type { Rule } from 'eslint';
11
17
  declare const rule: Rule.RuleModule;
@@ -6,8 +6,16 @@
6
6
  * Default: 700 lines
7
7
  *
8
8
  * Configuration:
9
- * '@webpieces/max-file-lines': ['error', { max: 700 }]
9
+ * '@webpieces/max-file-lines': ['error', { max: 700, allowedPaths: ['vendor-glob-here'] }]
10
+ *
11
+ * Machine-generated trees (the GENERATED_CODE_PATHS list in @webpieces/rules-config: __generated__
12
+ * and generated directories at any depth, .generated.ts / .generated.tsx files, and dist) are exempt
13
+ * with NO configuration, and `allowedPaths` ADDS your own un-authored trees to that floor — the same
14
+ * exemption the edit-time hook and the build-time validator apply, so one engine cannot pass a file
15
+ * the next one blocks. Never list hand-written code there; a long file you wrote gets refactored.
10
16
  */
17
+ const tslib_1 = require("tslib");
18
+ const path = tslib_1.__importStar(require("path"));
11
19
  const rules_config_1 = require("@webpieces/rules-config");
12
20
  const toError_1 = require("../toError");
13
21
  const workspace_root_1 = require("../workspace-root");
@@ -50,6 +58,10 @@ const rule = {
50
58
  type: 'integer',
51
59
  minimum: 1,
52
60
  },
61
+ allowedPaths: {
62
+ type: 'array',
63
+ items: { type: 'string' },
64
+ },
53
65
  },
54
66
  additionalProperties: false,
55
67
  },
@@ -58,6 +70,21 @@ const rule = {
58
70
  create(context) {
59
71
  const options = context.options[0];
60
72
  const maxLines = options?.max ?? 700;
73
+ // Machine-generated trees are exempt with no configuration, exactly as they are in the
74
+ // edit-time hook and the build-time validator — an exemption honoured by one engine and
75
+ // ignored by another is the same file passing here and blocking there. `allowedPaths` adds
76
+ // to that floor; it is for files this repo did not author, never for hand-written code.
77
+ const filename = context.filename || context.getFilename();
78
+ const relPath = path.relative(workspace.workspaceRoot(context), filename);
79
+ // Normally the workspace-relative path is what the globs are written against. When root
80
+ // resolution degenerates (no repo root above the file, or the file sits outside it) that path
81
+ // is a bare basename or a `../` climb and would hide a `**/__generated__/**` match, so the
82
+ // absolute path is judged instead — never in addition, so a repo checked out under a directory
83
+ // named `dist` or `generated` is not exempted wholesale.
84
+ const rooted = relPath !== '' && !relPath.startsWith('..') && relPath.includes(path.sep);
85
+ const judged = rooted ? relPath : filename;
86
+ if ((0, rules_config_1.isPathExcluded)(judged, [...rules_config_1.GENERATED_CODE_PATHS, ...(options?.allowedPaths ?? [])]))
87
+ return {};
61
88
  return {
62
89
  // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties
63
90
  Program(node) {
@@ -1 +1 @@
1
- {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/max-file-lines.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;AAGH,0DAAiE;AACjE,wCAAqC;AACrC,sDAAwD;AAExD,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAC9C,MAAM,SAAS,GAAG,IAAI,oCAAmB,EAAE,CAAC;AAM5C,uDAAuD;AACvD,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,SAAS,aAAa,CAAC,OAAyB;IAC5C,IAAI,cAAc;QAAE,OAAO;IAC3B,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACvD,8DAA8D;IAC9D,IAAI,CAAC;QACD,IAAA,qCAAsB,EAAC,aAAa,EAAE,aAAa,CAAC,CAAC;QACrD,cAAc,GAAG,IAAI,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,iBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,mDAAmD,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC;AACL,CAAC;AAED,MAAM,IAAI,GAAoB;IAC1B,IAAI,EAAE;QACF,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACF,WAAW,EAAE,6BAA6B;YAC1C,QAAQ,EAAE,gBAAgB;YAC1B,WAAW,EAAE,KAAK;YAClB,GAAG,EAAE,4CAA4C;SACpD;QACD,QAAQ,EAAE;YACN,OAAO,EACH,+IAA+I;SACtJ;QACD,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE;YACJ;gBACI,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACR,GAAG,EAAE;wBACD,IAAI,EAAE,SAAS;wBACf,OAAO,EAAE,CAAC;qBACb;iBACJ;gBACD,oBAAoB,EAAE,KAAK;aAC9B;SACJ;KACJ;IAED,MAAM,CAAC,OAAyB;QAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAiC,CAAC;QACnE,MAAM,QAAQ,GAAG,OAAO,EAAE,GAAG,IAAI,GAAG,CAAC;QAErC,OAAO;YACH,0FAA0F;YAC1F,OAAO,CAAC,IAAS;gBACb,aAAa,CAAC,OAAO,CAAC,CAAC;gBAEvB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;gBACjE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;gBAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;gBAE/B,IAAI,SAAS,GAAG,QAAQ,EAAE,CAAC;oBACvB,OAAO,CAAC,MAAM,CAAC;wBACX,IAAI;wBACJ,SAAS,EAAE,SAAS;wBACpB,IAAI,EAAE;4BACF,MAAM,EAAE,MAAM,CAAC,SAAS,CAAC;4BACzB,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC;yBACxB;qBACJ,CAAC,CAAC;gBACP,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC;AAEF,iBAAS,IAAI,CAAC","sourcesContent":["/**\n * ESLint rule to enforce maximum file length\n *\n * Enforces a configurable maximum line count for files.\n * Default: 700 lines\n *\n * Configuration:\n * '@webpieces/max-file-lines': ['error', { max: 700 }]\n */\n\nimport type { Rule } from 'eslint';\nimport { writeTemplateIfMissing } from '@webpieces/rules-config';\nimport { toError } from '../toError';\nimport { EslintWorkspaceRoot } from '../workspace-root';\n\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\nconst workspace = new EslintWorkspaceRoot();\n\ninterface FileLinesOptions {\n max: number;\n}\n\n// Module-level flag to prevent redundant file creation\nlet fileDocCreated = false;\n\nfunction ensureFileDoc(context: Rule.RuleContext): void {\n if (fileDocCreated) return;\n const workspaceRoot = workspace.workspaceRoot(context);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n writeTemplateIfMissing(workspaceRoot, INSTRUCT_FILE);\n fileDocCreated = true;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn('[webpieces] Could not write webpieces.filesize.md', error);\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Enforce maximum file length',\n category: 'Best Practices',\n recommended: false,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n tooLong:\n 'AI Agent: READ .webpieces/instruct-ai/webpieces.filesize.md (at the repo root) for fix instructions. File has {{actual}} lines (max: {{max}})',\n },\n fixable: undefined,\n schema: [\n {\n type: 'object',\n properties: {\n max: {\n type: 'integer',\n minimum: 1,\n },\n },\n additionalProperties: false,\n },\n ],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = context.options[0] as FileLinesOptions | undefined;\n const maxLines = options?.max ?? 700;\n\n return {\n // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties\n Program(node: any): void {\n ensureFileDoc(context);\n\n const sourceCode = context.sourceCode || context.getSourceCode();\n const lines = sourceCode.lines;\n const lineCount = lines.length;\n\n if (lineCount > maxLines) {\n context.report({\n node,\n messageId: 'tooLong',\n data: {\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}
1
+ {"version":3,"file":"max-file-lines.js","sourceRoot":"","sources":["../../../../../../packages/tooling/eslint-rules/src/rules/max-file-lines.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ESLint rule to enforce maximum file length\n *\n * Enforces a configurable maximum line count for files.\n * Default: 700 lines\n *\n * Configuration:\n * '@webpieces/max-file-lines': ['error', { max: 700, allowedPaths: ['vendor-glob-here'] }]\n *\n * Machine-generated trees (the GENERATED_CODE_PATHS list in @webpieces/rules-config: __generated__\n * and generated directories at any depth, .generated.ts / .generated.tsx files, and dist) are exempt\n * with NO configuration, and `allowedPaths` ADDS your own un-authored trees to that floor — the same\n * exemption the edit-time hook and the build-time validator apply, so one engine cannot pass a file\n * the next one blocks. Never list hand-written code there; a long file you wrote gets refactored.\n */\n\nimport type { Rule } from 'eslint';\nimport * as path from 'path';\nimport { writeTemplateIfMissing, isPathExcluded, GENERATED_CODE_PATHS } from '@webpieces/rules-config';\nimport { toError } from '../toError';\nimport { EslintWorkspaceRoot } from '../workspace-root';\n\nconst INSTRUCT_FILE = 'webpieces.filesize.md';\nconst workspace = new EslintWorkspaceRoot();\n\ninterface FileLinesOptions {\n max: number;\n allowedPaths?: string[];\n}\n\n// Module-level flag to prevent redundant file creation\nlet fileDocCreated = false;\n\nfunction ensureFileDoc(context: Rule.RuleContext): void {\n if (fileDocCreated) return;\n const workspaceRoot = workspace.workspaceRoot(context);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n writeTemplateIfMissing(workspaceRoot, INSTRUCT_FILE);\n fileDocCreated = true;\n } catch (err: unknown) {\n const error = toError(err);\n console.warn('[webpieces] Could not write webpieces.filesize.md', error);\n }\n}\n\nconst rule: Rule.RuleModule = {\n meta: {\n type: 'suggestion',\n docs: {\n description: 'Enforce maximum file length',\n category: 'Best Practices',\n recommended: false,\n url: 'https://github.com/deanhiller/webpieces-ts',\n },\n messages: {\n tooLong:\n 'AI Agent: READ .webpieces/instruct-ai/webpieces.filesize.md (at the repo root) for fix instructions. File has {{actual}} lines (max: {{max}})',\n },\n fixable: undefined,\n schema: [\n {\n type: 'object',\n properties: {\n max: {\n type: 'integer',\n minimum: 1,\n },\n allowedPaths: {\n type: 'array',\n items: { type: 'string' },\n },\n },\n additionalProperties: false,\n },\n ],\n },\n\n create(context: Rule.RuleContext): Rule.RuleListener {\n const options = context.options[0] as FileLinesOptions | undefined;\n const maxLines = options?.max ?? 700;\n\n // Machine-generated trees are exempt with no configuration, exactly as they are in the\n // edit-time hook and the build-time validator — an exemption honoured by one engine and\n // ignored by another is the same file passing here and blocking there. `allowedPaths` adds\n // to that floor; it is for files this repo did not author, never for hand-written code.\n const filename = context.filename || context.getFilename();\n const relPath = path.relative(workspace.workspaceRoot(context), filename);\n // Normally the workspace-relative path is what the globs are written against. When root\n // resolution degenerates (no repo root above the file, or the file sits outside it) that path\n // is a bare basename or a `../` climb and would hide a `**/__generated__/**` match, so the\n // absolute path is judged instead — never in addition, so a repo checked out under a directory\n // named `dist` or `generated` is not exempted wholesale.\n const rooted = relPath !== '' && !relPath.startsWith('..') && relPath.includes(path.sep);\n const judged = rooted ? relPath : filename;\n if (isPathExcluded(judged, [...GENERATED_CODE_PATHS, ...(options?.allowedPaths ?? [])])) return {};\n\n return {\n // webpieces-disable no-any-unknown -- ESTree AST nodes require any for dynamic properties\n Program(node: any): void {\n ensureFileDoc(context);\n\n const sourceCode = context.sourceCode || context.getSourceCode();\n const lines = sourceCode.lines;\n const lineCount = lines.length;\n\n if (lineCount > maxLines) {\n context.report({\n node,\n messageId: 'tooLong',\n data: {\n actual: String(lineCount),\n max: String(maxLines),\n },\n });\n }\n },\n };\n },\n};\n\nexport = rule;\n"]}