@alien_intelligence/eslint-plugin-nitpicker 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alien Intelligence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ <p align="center">
2
+ <br />
3
+ <a href="https://www.alien.club" target="_blank"><img width="64px" src="https://alien-website.cdn.prismic.io/alien-website/Zrn2b0aF0TcGI3Bu_alien-logo.svg" /></a>
4
+ <h2 align="center">@alien_intelligence/eslint-plugin-nitpicker</h2>
5
+ <p align="center">A hyper-pedantic ESLint plugin that flags every stylistic<br />and semantic nit, specifically for AI.</p>
6
+ </p>
@@ -0,0 +1,8 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+
3
+ /**
4
+ * Main declaration of the `@alien_intelligence/eslint-plugin-nitpicker` plugin.
5
+ */
6
+ declare const plugin: TSESLint.FlatConfig.Plugin;
7
+
8
+ export { plugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {ESLintUtils}from'@typescript-eslint/utils';var fe={PLUGIN_NAME:"nitpicker",REPO_URL:"https://github.com/the-alien-club/eslint-plugin-nitpicker",EM_DASH:"\u2014",WORD_CHAR:/[\p{L}\p{N}_$]/u,ADONIS_SUBPATH:/^#(models|controllers|services|middleware|validators|policies|config|start|database|providers|lib)\b/,REACT_FILE:/\.[jt]sx$/,BOX_DRAWING:/[─-▟]/,PURE_SEPARATOR:/^[-=~*#_+]{3,}$/,WRAPPED_LABEL:/^[-=~*#_+]{2,}\s.*\s[-=~*#_+]{2,}$/,SUB_WORD:/[A-Z]+(?![a-z])|[A-Z][a-z]+|[a-z]+/g},a=fe;var v={colour:"color",colours:"colors",coloured:"colored",colouring:"coloring",behaviour:"behavior",behaviours:"behaviors",favourite:"favorite",favourites:"favorites",flavour:"flavor",flavours:"flavors",honour:"honor",labour:"labor",neighbour:"neighbor",normalise:"normalize",normalised:"normalized",normalising:"normalizing",normalisation:"normalization",initialise:"initialize",initialised:"initialized",initialising:"initializing",initialisation:"initialization",serialise:"serialize",serialised:"serialized",serialising:"serializing",serialisation:"serialization",organise:"organize",organised:"organized",organising:"organizing",organisation:"organization",optimise:"optimize",optimised:"optimized",optimising:"optimizing",optimisation:"optimization",customise:"customize",customised:"customized",customising:"customizing",sanitise:"sanitize",sanitised:"sanitized",sanitising:"sanitizing",synchronise:"synchronize",synchronised:"synchronized",synchronising:"synchronizing",authorise:"authorize",authorised:"authorized",authorising:"authorizing",finalise:"finalize",finalised:"finalized",finalising:"finalizing",capitalise:"capitalize",capitalised:"capitalized",capitalising:"capitalizing",categorise:"categorize",categorised:"categorized",analyse:"analyze",analysed:"analyzed",analysing:"analyzing",centre:"center",centred:"centered",centres:"centers",fibre:"fiber",metre:"meter",licence:"license",defence:"defense",offence:"offense",cancelled:"canceled",cancelling:"canceling",labelled:"labeled",labelling:"labeling",modelling:"modeling",travelled:"traveled",grey:"gray",dialogue:"dialog",catalogue:"catalog"};var F=ESLintUtils.RuleCreator(e=>`${a.REPO_URL}/blob/main/docs/rules/${e}.md`);var m=class{toRuleModule(){return F({name:this.name,meta:this.meta,defaultOptions:this.defaultOptions,create:(t,n)=>this.create(t,n)})}};function P(e,t,n){let i={};for(let[o,s]of Object.entries(e))i[o.toLowerCase()]=s;for(let[o,s]of Object.entries(t))i[o.toLowerCase()]=s;for(let o of n)delete i[o.toLowerCase()];return i}function d({problem:e,why:t,fix:n}){return `${e}
2
+ - why: ${t}
3
+ - fix: ${n}`}function R(e){let t=[];for(let n of e.matchAll(a.SUB_WORD))n.index!==void 0&&t.push({text:n[0],index:n.index});return t}function x(e,t){return e===e.toUpperCase()?t.toUpperCase():e.charAt(0)===e.charAt(0).toUpperCase()?t.charAt(0).toUpperCase()+t.slice(1):t}var b=class extends m{name="no-british-english";defaultOptions=[{extra:{},ignore:[]}];meta={type:"suggestion",docs:{description:"Disallow British English spellings in identifiers and comments.",recommended:true,category:"base"},fixable:"code",schema:[{type:"object",properties:{extra:{type:"object",additionalProperties:{type:"string"}},ignore:{type:"array",items:{type:"string"}}},additionalProperties:false}],messages:{british:d({problem:"British spelling `{{british}}`, this codebase uses American English.",why:"One spelling convention keeps identifiers and docs consistent and searchable",fix:"Use `{{american}}` instead"})}};create(t,n){let i=P(v,n[0]?.extra??{},n[0]?.ignore??[]),{sourceCode:o}=t;return {Identifier(s){if(!(s.parent.type==="MemberExpression"&&s.parent.property===s&&!s.parent.computed))for(let r of R(s.name)){let p=i[r.text.toLowerCase()];p!==void 0&&t.report({node:s,messageId:"british",data:{british:r.text,american:x(r.text,p)}});}},Program(){for(let s of o.getAllComments())for(let r of R(s.value)){let p=i[r.text.toLowerCase()];if(p===void 0)continue;let u=x(r.text,p),f=s.range[0]+2+r.index,y=f+r.text.length;t.report({loc:{start:o.getLocFromIndex(f),end:o.getLocFromIndex(y)},messageId:"british",data:{british:r.text,american:u},fix:ue=>ue.replaceTextRange([f,y],u)});}}}}},_=new b;function z(e){let t=e.replace(/^\s*\*?\s*/,"").trimEnd();return t.length===0?false:a.BOX_DRAWING.test(t)||a.PURE_SEPARATOR.test(t)||a.WRAPPED_LABEL.test(t)}function ye(e){let t=e.replace(/\\/g,"/"),n="^";for(let i=0;i<t.length;i++){let o=t[i];if(o===void 0)break;o==="*"?t[i+1]==="*"?(n+=".*",i++,t[i+1]==="/"&&i++):n+="[^/]*":"\\^$.|?+()[]{}".includes(o)?n+=`\\${o}`:n+=o;}return new RegExp(`${n}$`)}function U(e,t){let n=e.replace(/\\/g,"/");return t.some(i=>ye(i).test(n))}var L=class extends m{name="no-decorative-comment-separators";defaultOptions=[{allowIn:[]}];meta={type:"layout",docs:{description:"Disallow decorative separators (banners, box-drawing, repeated dashes) inside comments.",recommended:true,category:"base"},schema:[{type:"object",properties:{allowIn:{type:"array",items:{type:"string"}}},additionalProperties:false}],messages:{decorative:d({problem:"This comment uses a decorative separator.",why:"Repeated separator characters and box-drawing lines are visual noise that add nothing over a plain label",fix:"Remove the separator, a one-line label or a blank line already divides sections clearly"})}};create(t,n){let i=n[0]?.allowIn??[];if(i.length>0&&U(t.filename,i))return {};let{sourceCode:o}=t;return {Program(){for(let s of o.getAllComments()){let r=s.value.split(`
4
+ `);for(let p=0;p<r.length;p++){let u=r[p];if(u===void 0||!z(u))continue;let f=s.loc.start.line+p,y=o.lines[f-1]??"";t.report({loc:{start:{line:f,column:0},end:{line:f,column:y.length}},messageId:"decorative"});}}}}}},j=new L;var N=class extends m{name="no-em-dash";defaultOptions=[];meta={type:"suggestion",docs:{description:"Disallow the em dash (\u2014) character anywhere in the source.",recommended:true},schema:[],messages:{emDash:d({problem:"Found an em dash (\u2014) character.",why:"Em dashes are typically introduced by AI-generated or auto-formatted text and are discouraged here.",fix:"Replace the em dash with a hyphen (-), a comma (,), or reword the sentence to avoid it."})}};create(t){let{sourceCode:n}=t,i=n.getText();return {Program(){for(let o=0;o<i.length;o++)i[o]===a.EM_DASH&&t.report({loc:{start:n.getLocFromIndex(o),end:n.getLocFromIndex(o+1)},messageId:"emDash"});}}}},$=new N;function h(e){return e.type==="Block"&&e.value.startsWith("*")}function J(e,t){let i=e.getCommentsBefore(t).at(-1);return i!==void 0&&h(i)}function k(e){return /^\s*\*\s*$/.test(e)}function B(e){return /^\s*\*\s*@/.test(e)}var C=class extends m{name="no-jsdoc-blank-before-tags";defaultOptions=[];meta={type:"layout",fixable:"code",docs:{description:"Disallow blank lines before JSDoc tags such as `@param` or `@returns`.",recommended:true},schema:[],messages:{blankBeforeTag:d({problem:"There is a blank line before a JSDoc tag.",why:"Tags should follow the description directly; an empty line there is noise that inflates the comment.",fix:"Remove the blank line so the tag follows on directly."})}};create(t){let{sourceCode:n}=t;return {Program(){for(let i of n.getAllComments())if(h(i)&&i.loc.start.line!==i.loc.end.line)for(let o=i.loc.start.line;o<=i.loc.end.line;o++){let s=n.lines[o-1];if(s===void 0||!k(s))continue;let r=o;for(;r<i.loc.end.line&&k(n.lines[r]??"");)r++;let p=n.lines[r];if(p!==void 0&&B(p)){let u=n.getIndexFromLoc({line:o,column:0}),f=n.getIndexFromLoc({line:r+1,column:0});t.report({loc:{start:{line:o,column:0},end:{line:r,column:s.length}},messageId:"blankBeforeTag",fix:y=>y.removeRange([u,f])});}o=r;}}}}},G=new C;function W(e){return e!==void 0&&a.WORD_CHAR.test(e)}var O=class extends m{name="no-line-comment-period";defaultOptions=[];meta={type:"layout",fixable:"code",docs:{description:"Disallow prose periods in `//` line comments (code-reference dots and ellipses are allowed).",recommended:true},schema:[],messages:{period:d({problem:"This line comment contains a period.",why:"Line comments should be short, clear fragments, not full sentences, so periods are just noise, dots inside code references like `foo.bar` are allowed.",fix:"Remove the period and keep the comment terse."})}};create(t){let{sourceCode:n}=t;return {Program(){for(let i of n.getAllComments()){if(i.type!=="Line")continue;let{value:o}=i,s=i.range[0]+2;for(let r=0;r<o.length;r++){if(o[r]!==".")continue;if(o[r+1]==="."){for(;o[r+1]===".";)r++;continue}if(W(o[r+1]))continue;let p=s+r;t.report({loc:{start:n.getLocFromIndex(p),end:n.getLocFromIndex(p+1)},messageId:"period",fix:u=>u.removeRange([p,p+1])});}}}}}},V=new O;function H(e){let t=e,n=false;for(;;){if(t.type==="ChainExpression"||t.type==="TSNonNullExpression"){t=t.expression;continue}if(t.type==="MemberExpression"){if(t.computed)return false;n=true,t=t.object;continue}break}return n&&(t.type==="Identifier"||t.type==="ThisExpression")}var I=class extends m{name="no-property-access-alias";defaultOptions=[];meta={type:"suggestion",docs:{description:"Disallow a `const` whose whole value is a single property access, inline the expression instead.",recommended:true,category:"base"},schema:[],messages:{propertyAccessAlias:d({problem:"`{{name}}` only aliases the property access `{{expression}}`.",why:"A variable that just renames a property hides where the value comes from when scanning the code",fix:"Remove it and use `{{expression}}` inline, or use `let` if it is reassigned later"})}};create(t){let{sourceCode:n}=t;return {VariableDeclarator(i){i.parent.type!=="VariableDeclaration"||i.parent.kind!=="const"||i.parent.parent.type!=="ExportNamedDeclaration"&&(i.id.type!=="Identifier"||i.init===null||H(i.init)&&t.report({node:i,messageId:"propertyAccessAlias",data:{name:i.id.name,expression:n.getText(i.init)}}));}}}},q=new I;var w=class extends m{name="no-single-line-jsdoc";defaultOptions=[];meta={type:"layout",fixable:"code",docs:{description:"Require JSDoc comments to span multiple lines rather than sit on a single line.",recommended:true},schema:[],messages:{singleLine:d({problem:"This JSDoc comment is written on a single line.",why:"Multi-line JSDoc is easier to read, diff, and extend with additional tags, and is the house style.",fix:"Put the opening `/**`, the ` * ` content, and the closing `*/` each on their own line."})}};create(t){let{sourceCode:n}=t;return {Program(){for(let i of n.getAllComments()){if(!h(i)||i.loc.start.line!==i.loc.end.line)continue;let o=i.value.replace(/^\*/,"").trim();o.length!==0&&t.report({loc:i.loc,messageId:"singleLine",fix(s){let r=" ".repeat(i.loc.start.column),p=`/**
5
+ ${r} * ${o}
6
+ ${r} */`;return s.replaceTextRange(i.range,p)}});}}}}},K=new w;var X={adonisjs:"AdonisJS",react:"React"};function Se(e){let t=[];for(let n of e.ast.body)n.type==="ImportDeclaration"&&t.push(String(n.source.value));return t}function Z(e,t){let n=new Set,i=Se(e);return i.some(s=>s.startsWith("@adonisjs/")||a.ADONIS_SUBPATH.test(s))&&n.add("adonisjs"),(i.some(s=>s==="react"||s.startsWith("react/")||s==="react-dom")||a.REACT_FILE.test(t))&&n.add("react"),n}var A=class extends m{name="require-framework-config";defaultOptions=[{ignore:[]}];meta={type:"suggestion",docs:{description:"Warn when a file uses a framework whose Nitpicker config is not enabled.",recommended:true,category:"base"},schema:[{type:"object",properties:{ignore:{type:"array",items:{type:"string",enum:["adonisjs","react"]}}},additionalProperties:false}],messages:{missingConfig:d({problem:"This file uses {{framework}} but the Nitpicker {{framework}} rules are not enabled.",why:"Framework rules only run when you opt into the matching config, so files like this one go unchecked",fix:"Add `nitpicker.configs.{{config}}` (scoped to these files) to your ESLint config, or turn off `nitpicker/require-framework-config`"})}};create(t,n){let i=new Set(n[0]?.ignore??[]),o=t.settings[a.PLUGIN_NAME]??{};return {Program(s){let r=Z(t.sourceCode,t.filename);for(let p of r)i.has(p)||o[p]||t.report({node:s,messageId:"missingConfig",data:{framework:X[p],config:p}});}}}},Y=new A;function Q(e){let t=e;return t.parent.type==="VariableDeclarator"&&t.parent.parent.type==="VariableDeclaration"&&(t=t.parent.parent),(t.parent.type==="ExportNamedDeclaration"||t.parent.type==="ExportDefaultDeclaration")&&(t=t.parent),t}function ee(e){return e.parent?.type==="Program"}function te(e){if((e.type==="FunctionDeclaration"||e.type==="FunctionExpression")&&e.id)return e.id.name;if(e.parent.type==="VariableDeclarator"&&e.parent.id.type==="Identifier")return e.parent.id.name}var he=new Set(["FunctionDeclaration","FunctionExpression","ArrowFunctionExpression"]);function ie(e){return /^[A-Z]/.test(e)}function g(e){if(!e)return false;switch(e.type){case "JSXElement":case "JSXFragment":return true;case "ConditionalExpression":return g(e.consequent)||g(e.alternate);case "LogicalExpression":return g(e.left)||g(e.right);case "SequenceExpression":return g(e.expressions.at(-1));default:return false}}function ne(e,t){if(e.type==="ReturnStatement")return g(e.argument);for(let n of t[e.type]??[]){let i=e[n],o=Array.isArray(i)?i:[i];for(let s of o){let r=s;if(!(!r||typeof r.type!="string")&&!he.has(r.type)&&ne(r,t))return true}}return false}function oe(e,t){return e.type==="ArrowFunctionExpression"&&e.body.type!=="BlockStatement"?g(e.body):ne(e.body,t)}var D=class extends m{name="require-function-jsdoc";defaultOptions=[];meta={type:"suggestion",docs:{description:"Require a JSDoc comment on top-level functions, except React component functions.",recommended:true},schema:[],messages:{missingJSDoc:d({problem:"The function `{{name}}` has no JSDoc comment.",why:"Top-level functions must document their purpose, parameters, and return value, React component functions are the only exception.",fix:"Add a `/** ... */` JSDoc block immediately above the function describing what it does."})}};create(t){let{sourceCode:n}=t,i=(o,s)=>{let r=te(o);if(r===void 0)return;let p=Q(o);ee(p)&&(ie(r)&&oe(o,n.visitorKeys)||J(n,p)||t.report({node:s,messageId:"missingJSDoc",data:{name:r}}));};return {FunctionDeclaration(o){i(o,o.id??o);},VariableDeclarator(o){let{init:s}=o;s&&(s.type!=="ArrowFunctionExpression"&&s.type!=="FunctionExpression"||i(s,o.id));}}}},re=new D;var Ee=[_,j,$,G,V,q,K,Y,re],E=Object.fromEntries(Ee.map(e=>[e.name,e.toRuleModule()]));function S(e){let t={};for(let[n,i]of Object.entries(E))(i.meta.docs?.category??"base")===e&&(t[`${a.PLUGIN_NAME}/${n}`]="warn");return t}function se(){let e={};for(let t of Object.keys(E))e[`${a.PLUGIN_NAME}/${t}`]="warn";return e}function ae(e){return {name:`${a.PLUGIN_NAME}/all`,plugins:{[a.PLUGIN_NAME]:e},settings:{[a.PLUGIN_NAME]:{adonisjs:true,react:true}},rules:se()}}function T(e){return {name:`${a.PLUGIN_NAME}/base`,plugins:{[a.PLUGIN_NAME]:e},rules:S("base")}}function le(e){return {...T(e),name:`${a.PLUGIN_NAME}/recommended`}}var Te=["**/start/routes.ts","**/start/routes/**/*.ts"];function ce(e){return {name:`${a.PLUGIN_NAME}/adonisjs`,plugins:{[a.PLUGIN_NAME]:e},settings:{[a.PLUGIN_NAME]:{adonisjs:true}},rules:{...S("adonisjs"),[`${a.PLUGIN_NAME}/no-decorative-comment-separators`]:["warn",{allowIn:Te}]}}}function pe(e){return {name:`${a.PLUGIN_NAME}/react`,plugins:{[a.PLUGIN_NAME]:e},settings:{[a.PLUGIN_NAME]:{react:true}},rules:S("react")}}function me(e){return {base:T(e),recommended:le(e),adonisjs:ce(e),react:pe(e),all:ae(e)}}var de="0.1.0";var M={meta:{name:`eslint-plugin-${a.PLUGIN_NAME}`,version:de},rules:E,configs:{}};M.configs=me(M);var Ki=M;export{Ki as default};//# sourceMappingURL=index.js.map
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/constants.ts","../src/lib/data/britishToAmerican.ts","../src/lib/utils/createRule.ts","../src/lib/rule.ts","../src/lib/utils/dictionary.ts","../src/lib/utils/nitpick.ts","../src/lib/utils/words.ts","../src/rules/base/noBritishEnglish.ts","../src/lib/utils/decoration.ts","../src/lib/utils/matchesGlob.ts","../src/rules/base/noDecorativeCommentSeparators.ts","../src/rules/base/noEmDash.ts","../src/lib/utils/JSDoc.ts","../src/rules/base/noJSDocBlankBeforeTags.ts","../src/lib/utils/isWordChar.ts","../src/rules/base/noLineCommentPeriod.ts","../src/lib/utils/aliases.ts","../src/rules/base/noPropertyAccessAlias.ts","../src/rules/base/noSingleLineJSDoc.ts","../src/lib/utils/framework.ts","../src/rules/base/requireFrameworkConfig.ts","../src/lib/utils/functions.ts","../src/lib/utils/react.ts","../src/rules/base/requireFunctionJSDoc.ts","../src/rules/index.ts","../src/configs/helpers.ts","../src/configs/presets/all.ts","../src/configs/rulesets/base.ts","../src/configs/presets/recommended.ts","../src/configs/rulesets/adonisjs.ts","../src/configs/rulesets/react.ts","../src/configs/index.ts","../package.json","../src/index.ts"],"names":["CONSTANTS","constants_default","BRITISH_TO_AMERICAN","createRule","ESLintUtils","name","NitpickerRule","context","options","buildDictionary","base","extra","ignore","dictionary","key","value","nitpick","problem","why","fix","extractWords","text","words","match","matchCase","source","replacement","NoBritishEnglish","sourceCode","node","word","american","comment","cased","from","to","fixer","noBritishEnglish_default","isDecorativeCommentLine","line","content","globToRegExp","glob","normalized","pattern","index","char","matchesGlob","filename","patterns","path","NoDecorativeCommentSeparators","allowIn","lines","reportedLine","noDecorativeCommentSeparators_default","NoEmDash","noEmDash_default","isJSDocComment","hasLeadingJSDoc","closest","isBlankJSDocLine","isJSDocTagLine","NoJSDocBlankBeforeTags","runEnd","nextLine","noJSDocBlankBeforeTags_default","isWordChar","NoLineCommentPeriod","valueStart","at","noLineCommentPeriod_default","isPropertyAccessAlias","expression","sawMemberAccess","NoPropertyAccessAlias","noPropertyAccessAlias_default","NoSingleLineJSDoc","indent","expanded","noSingleLineJSDoc_default","FRAMEWORK_LABELS","importSources","sources","statement","detectFrameworks","detected","RequireFrameworkConfig","ignored","enabled","framework","requireFrameworkConfig_default","getDocumentableNode","fn","isTopLevel","getFunctionName","FUNCTION_NODE_TYPES","isReactComponentName","isJsxExpression","subtreeReturnsJsx","visitorKeys","children","child","childNode","functionReturnsJsx","RequireFunctionJSDoc","check","reportNode","documentable","init","requireFunctionJSDoc_default","ruleInstances","rules","rule","categoryRules","category","allRules","all","plugin","recommended","ROUTE_FILE_GLOBS","adonisjs","react","buildConfigs","version","index_default"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * The global set of constants shared across the plugin.\n */\nconst CONSTANTS = {\n /**\n * The short name the plugin is registered under inside an ESLint config,\n * e.g. `nitpicker/no-em-dash`.\n */\n PLUGIN_NAME: \"nitpicker\",\n\n /**\n * The base URL of the plugin's repository.\n */\n REPO_URL: \"https://github.com/the-alien-club/eslint-plugin-nitpicker\",\n\n /**\n * The em dash character (U+2014), e.g. \"—\".\n */\n EM_DASH: \"—\",\n\n /**\n * Matches a single \"word\" character, i.e. anything that can appear inside an\n * identifier or a number (letters, digits, underscore and dollar).\n */\n WORD_CHAR: /[\\p{L}\\p{N}_$]/u,\n\n /**\n * AdonisJS subpath import roots (`#models/...`, `#controllers/...`, etc.).\n */\n ADONIS_SUBPATH:\n /^#(models|controllers|services|middleware|validators|policies|config|start|database|providers|lib)\\b/,\n\n /**\n * React source files by extension.\n */\n REACT_FILE: /\\.[jt]sx$/,\n\n /**\n * Box-drawing and block-element characters, which are always decorative\n * when found inside a comment.\n */\n BOX_DRAWING: /[─-▟]/,\n\n /**\n * A comment line made entirely of three or more repeated separator\n * characters, e.g. `======` or `------`.\n */\n PURE_SEPARATOR: /^[-=~*#_+]{3,}$/,\n\n /**\n * A short label fenced by separator runs inside a comment, e.g `-- Section --`.\n */\n WRAPPED_LABEL: /^[-=~*#_+]{2,}\\s.*\\s[-=~*#_+]{2,}$/,\n\n /**\n * Matches a single sub-word: an all-caps acronym, a capitalized word, or a\n * lowercase run, so `getUserName` splits into \"get\", \"User\" and \"Name\"\n */\n SUB_WORD: /[A-Z]+(?![a-z])|[A-Z][a-z]+|[a-z]+/g,\n} as const\n\nexport default CONSTANTS\n","/**\n * A dictionary of British English spellings mapped to their American English\n * equivalents, keyed in lowercase, focused on the words that show up in code\n * and that AIs most often get wrong, consumers can extend it per project via the\n * `no-british-english` rule's `extra` option.\n */\nexport const BRITISH_TO_AMERICAN: Record<string, string> = {\n // \"-our\" to \"-or\"\n colour: \"color\",\n colours: \"colors\",\n coloured: \"colored\",\n colouring: \"coloring\",\n behaviour: \"behavior\",\n behaviours: \"behaviors\",\n favourite: \"favorite\",\n favourites: \"favorites\",\n flavour: \"flavor\",\n flavours: \"flavors\",\n honour: \"honor\",\n labour: \"labor\",\n neighbour: \"neighbor\",\n\n // \"-ise\" to \"-ize\" and their inflections\n normalise: \"normalize\",\n normalised: \"normalized\",\n normalising: \"normalizing\",\n normalisation: \"normalization\",\n initialise: \"initialize\",\n initialised: \"initialized\",\n initialising: \"initializing\",\n initialisation: \"initialization\",\n serialise: \"serialize\",\n serialised: \"serialized\",\n serialising: \"serializing\",\n serialisation: \"serialization\",\n organise: \"organize\",\n organised: \"organized\",\n organising: \"organizing\",\n organisation: \"organization\",\n optimise: \"optimize\",\n optimised: \"optimized\",\n optimising: \"optimizing\",\n optimisation: \"optimization\",\n customise: \"customize\",\n customised: \"customized\",\n customising: \"customizing\",\n sanitise: \"sanitize\",\n sanitised: \"sanitized\",\n sanitising: \"sanitizing\",\n synchronise: \"synchronize\",\n synchronised: \"synchronized\",\n synchronising: \"synchronizing\",\n authorise: \"authorize\",\n authorised: \"authorized\",\n authorising: \"authorizing\",\n finalise: \"finalize\",\n finalised: \"finalized\",\n finalising: \"finalizing\",\n capitalise: \"capitalize\",\n capitalised: \"capitalized\",\n capitalising: \"capitalizing\",\n categorise: \"categorize\",\n categorised: \"categorized\",\n\n // \"-yse\" to \"-yze\"\n analyse: \"analyze\",\n analysed: \"analyzed\",\n analysing: \"analyzing\",\n\n // \"-re\" to \"-er\"\n centre: \"center\",\n centred: \"centered\",\n centres: \"centers\",\n fibre: \"fiber\",\n metre: \"meter\",\n\n // \"-ce\" to \"-se\"\n licence: \"license\",\n defence: \"defense\",\n offence: \"offense\",\n\n // Doubled \"l\" in inflections\n cancelled: \"canceled\",\n cancelling: \"canceling\",\n labelled: \"labeled\",\n labelling: \"labeling\",\n modelling: \"modeling\",\n travelled: \"traveled\",\n\n // Miscellaneous\n grey: \"gray\",\n dialogue: \"dialog\",\n catalogue: \"catalog\",\n}\n","import { ESLintUtils } from \"@typescript-eslint/utils\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * The category a rule belongs to, which decides the shared config it ships in,\n * `base` rules are universal, framework categories only apply when the consumer\n * opts into the matching config.\n */\nexport type RuleCategory = \"base\" | \"adonisjs\" | \"react\"\n\n/**\n * Extra metadata attached to every Nitpicker rule under `meta.docs`.\n */\nexport type NitpickerRuleDocs = {\n /**\n * A short, human-readable description of what the rule enforces.\n */\n description: string\n\n /**\n * Whether the rule is part of the `recommended` shared config.\n */\n recommended?: boolean\n\n /**\n * The category the rule belongs to, defaults to `base` when omitted.\n */\n category?: RuleCategory\n}\n\n/**\n * The shared rule factory for the whole plugin.\n *\n * It wires up strong typing for a rule's options and message IDs, and points\n * every rule at its documentation page on GitHub.\n */\nexport const createRule = ESLintUtils.RuleCreator<NitpickerRuleDocs>(\n name => `${CONSTANTS.REPO_URL}/blob/main/docs/rules/${name}.md`,\n)\n","import { ESLintUtils, type TSESLint } from \"@typescript-eslint/utils\"\nimport { createRule, type NitpickerRuleDocs } from \"@/lib/utils/createRule\"\n\n/**\n * The base class every Nitpicker rule extends.\n *\n * A rule is expressed as a class so that shared behavior, typing, and metadata\n * live in one place, while each concrete rule only has to declare its name,\n * metadata, default options, and visitor logic, and call {@link toRuleModule} to\n * turn an instance into the plain object ESLint expects.\n */\nexport abstract class NitpickerRule<MessageIds extends string = string, Options extends readonly unknown[] = []> {\n /**\n * The kebab-case name of the rule, without the plugin prefix\n * (e.g. `no-em-dash`).\n */\n abstract readonly name: string\n\n /**\n * The ESLint metadata describing the rule (type, docs, schema, messages).\n */\n abstract readonly meta: ESLintUtils.NamedCreateRuleMeta<MessageIds, NitpickerRuleDocs, Options>\n\n /**\n * The options applied when the rule is enabled without an explicit config.\n */\n abstract readonly defaultOptions: Options\n\n /**\n * The visitor factory ESLint calls for every linted file.\n * @param context The rule context for the current file.\n * @param options The user options merged with {@link defaultOptions}.\n * @returns The AST visitor listeners.\n */\n abstract create(\n context: Readonly<TSESLint.RuleContext<MessageIds, Options>>,\n options: Readonly<Options>,\n ): TSESLint.RuleListener\n\n /**\n * Builds the ESLint-compatible rule module from this instance.\n * @returns The plain rule module object consumed by ESLint.\n */\n toRuleModule(): TSESLint.RuleModule<MessageIds, Options, NitpickerRuleDocs> {\n return createRule<Options, MessageIds>({\n name: this.name,\n meta: this.meta,\n defaultOptions: this.defaultOptions,\n create: (context, options) => this.create(context, options),\n })\n }\n}\n","/**\n * Merges a base lookup with extra entries and removes ignored keys, keying everything in lowercase\n * for case-insensitive lookups. Used to let a rule's built-in table be extended or narrowed\n * through its options.\n * @param base The built-in lookup.\n * @param extra Additional entries to add or override.\n * @param ignore Keys to remove from the result.\n * @returns The merged, lowercase-keyed lookup.\n */\nexport function buildDictionary(\n base: Record<string, string>,\n extra: Record<string, string>,\n ignore: string[],\n): Record<string, string> {\n const dictionary: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(base)) {\n dictionary[key.toLowerCase()] = value\n }\n\n for (const [key, value] of Object.entries(extra)) {\n dictionary[key.toLowerCase()] = value\n }\n\n for (const key of ignore) {\n delete dictionary[key.toLowerCase()]\n }\n\n return dictionary\n}\n","/**\n * The building blocks of a Nitpicker message, every rule reports its findings\n * through this shape so that the output is consistent and, crucially,\n * self-explanatory enough for an AI (or a human) to fix the issue without\n * opening the rule's documentation.\n */\nexport type Nitpick = {\n /**\n * What is wrong, stated plainly. May contain ESLint `{{placeholders}}`.\n */\n problem: string\n\n /**\n * Why it is worth fixing, so the reader understands the intent.\n */\n why: string\n\n /**\n * A concrete, actionable instruction describing how to fix it.\n */\n fix: string\n}\n\n/**\n * Composes a {@link Nitpick} into a single, richly-contextualized message\n * string suitable for a rule's `meta.messages` entry.\n *\n * ESLint `{{placeholders}}` inside any field are preserved untouched, so they\n * can still be interpolated with `data` at report time.\n * @param nitpick The problem/why/fix triplet to format.\n * @returns The formatted, multi-line message.\n */\nexport function nitpick({ problem, why, fix }: Nitpick): string {\n return `${problem}\\n - why: ${why}\\n - fix: ${fix}`\n}\n","import CONSTANTS from \"@/lib/constants\"\n\n/**\n * A sub-word found in a piece of text, with its start offset.\n */\nexport type Word = {\n /**\n * The sub-word, in its original casing.\n */\n text: string\n\n /**\n * The offset of the sub-word within the source text.\n */\n index: number\n}\n\n/**\n * Splits a piece of text into its sub-words, handling camelCase, PascalCase,\n * snake_case, and plain prose.\n * @param text The text to split.\n * @returns The sub-words found, each with its offset in the text.\n */\nexport function extractWords(text: string): Word[] {\n const words: Word[] = []\n\n for (const match of text.matchAll(CONSTANTS.SUB_WORD)) {\n if (match.index !== undefined) {\n words.push({ text: match[0], index: match.index })\n }\n }\n\n return words\n}\n\n/**\n * Rewrites a replacement word to match the casing of the word it replaces, so a\n * capitalized source yields a capitalized result and an all-caps source an\n * all-caps one.\n * @param source The original word whose casing should be mirrored.\n * @param replacement The lowercase replacement word.\n * @returns The replacement, cased like the source.\n */\nexport function matchCase(source: string, replacement: string): string {\n if (source === source.toUpperCase()) {\n return replacement.toUpperCase()\n }\n\n if (source.charAt(0) === source.charAt(0).toUpperCase()) {\n return replacement.charAt(0).toUpperCase() + replacement.slice(1)\n }\n\n return replacement\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { BRITISH_TO_AMERICAN } from \"@/lib/data/britishToAmerican\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { buildDictionary } from \"@/lib/utils/dictionary\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\nimport { extractWords, matchCase } from \"@/lib/utils/words\"\n\ntype Options = [{ extra: Record<string, string>; ignore: string[] }]\ntype MessageIds = \"british\"\n\n/**\n * Flags British English spellings in identifiers and comments, reporting the\n * American equivalent. The built-in dictionary can be extended per project with\n * the `extra` option, or narrowed with `ignore`.\n */\nclass NoBritishEnglish extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-british-english\"\n\n readonly defaultOptions: Options = [{ extra: {}, ignore: [] }]\n\n readonly meta = {\n type: \"suggestion\",\n docs: {\n description: \"Disallow British English spellings in identifiers and comments.\",\n recommended: true,\n category: \"base\",\n },\n fixable: \"code\",\n schema: [\n {\n type: \"object\",\n properties: {\n extra: {\n type: \"object\",\n additionalProperties: { type: \"string\" },\n },\n ignore: {\n type: \"array\",\n items: { type: \"string\" },\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n british: nitpick({\n problem: \"British spelling `{{british}}`, this codebase uses American English.\",\n why: \"One spelling convention keeps identifiers and docs consistent and searchable\",\n fix: \"Use `{{american}}` instead\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>, options: Options): TSESLint.RuleListener {\n const dictionary = buildDictionary(BRITISH_TO_AMERICAN, options[0]?.extra ?? {}, options[0]?.ignore ?? [])\n const { sourceCode } = context\n\n return {\n Identifier(node) {\n // Skip a member's property name (an external read), it cannot be\n // renamed from here\n if (node.parent.type === \"MemberExpression\" && node.parent.property === node && !node.parent.computed) {\n return\n }\n\n for (const word of extractWords(node.name)) {\n const american = dictionary[word.text.toLowerCase()]\n if (american === undefined) continue\n\n context.report({\n node,\n messageId: \"british\",\n data: { british: word.text, american: matchCase(word.text, american) },\n })\n }\n },\n Program() {\n for (const comment of sourceCode.getAllComments()) {\n for (const word of extractWords(comment.value)) {\n const american = dictionary[word.text.toLowerCase()]\n if (american === undefined) continue\n\n const cased = matchCase(word.text, american)\n\n // The comment value starts right after the `//` or `/*`\n const from = comment.range[0] + 2 + word.index\n const to = from + word.text.length\n\n context.report({\n loc: {\n start: sourceCode.getLocFromIndex(from),\n end: sourceCode.getLocFromIndex(to),\n },\n messageId: \"british\",\n data: { british: word.text, american: cased },\n fix: fixer => fixer.replaceTextRange([from, to], cased),\n })\n }\n }\n },\n }\n }\n}\n\nexport default new NoBritishEnglish()\n","import CONSTANTS from \"@/lib/constants\"\n\n/**\n * Checks whether a single comment line is a decorative separator, i.e. a banner,\n * a box-drawing rule, or a label fenced by repeated separator characters.\n * @param line The raw comment line, still carrying any leading ` * ` marker.\n * @returns `true` if the line is decorative.\n */\nexport function isDecorativeCommentLine(line: string): boolean {\n const content = line.replace(/^\\s*\\*?\\s*/, \"\").trimEnd()\n if (content.length === 0) return false\n\n return (\n CONSTANTS.BOX_DRAWING.test(content) ||\n CONSTANTS.PURE_SEPARATOR.test(content) ||\n CONSTANTS.WRAPPED_LABEL.test(content)\n )\n}\n","/**\n * Converts a glob pattern into an anchored regular expression, supporting `*`\n * (any run within a path segment) and `**` (any run across segments).\n * @param glob The glob pattern to convert.\n * @returns The equivalent regular expression.\n */\nfunction globToRegExp(glob: string): RegExp {\n const normalized = glob.replace(/\\\\/g, \"/\")\n let pattern = \"^\"\n\n for (let index = 0; index < normalized.length; index++) {\n const char = normalized[index]\n if (char === undefined) break\n\n if (char === \"*\") {\n if (normalized[index + 1] === \"*\") {\n pattern += \".*\"\n index++\n\n // If the `**` is followed by a `/`, skip it so that `**/` and `**` are equivalent\n if (normalized[index + 1] === \"/\") index++\n } else {\n pattern += \"[^/]*\"\n }\n } else if (\"\\\\^$.|?+()[]{}\".includes(char)) {\n pattern += `\\\\${char}`\n } else {\n pattern += char\n }\n }\n\n return new RegExp(`${pattern}$`)\n}\n\n/**\n * Checks whether a file path matches any of the given glob patterns, comparing\n * with forward slashes so it works the same on every platform.\n * @param filename The file path to test.\n * @param patterns The glob patterns to match against.\n * @returns `true` if the path matches at least one pattern.\n */\nexport function matchesGlob(filename: string, patterns: string[]): boolean {\n const path = filename.replace(/\\\\/g, \"/\")\n\n return patterns.some(pattern => globToRegExp(pattern).test(path))\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { isDecorativeCommentLine } from \"@/lib/utils/decoration\"\nimport { matchesGlob } from \"@/lib/utils/matchesGlob\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = [{ allowIn: string[] }]\ntype MessageIds = \"decorative\"\n\n/**\n * Flags decorative separators inside comments, such as banner rules\n * (`// ======`), box-drawing lines, and labels fenced by repeated dashes\n * (`// -- Section --`). The `allowIn` option lists globs where they are\n * tolerated, which the AdonisJS config uses to permit banners in route files.\n */\nclass NoDecorativeCommentSeparators extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-decorative-comment-separators\"\n\n readonly defaultOptions: Options = [{ allowIn: [] }]\n\n readonly meta = {\n type: \"layout\",\n docs: {\n description: \"Disallow decorative separators (banners, box-drawing, repeated dashes) inside comments.\",\n recommended: true,\n category: \"base\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n allowIn: {\n type: \"array\",\n items: { type: \"string\" },\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n decorative: nitpick({\n problem: \"This comment uses a decorative separator.\",\n why: \"Repeated separator characters and box-drawing lines are visual noise that add nothing over a plain label\",\n fix: \"Remove the separator, a one-line label or a blank line already divides sections clearly\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>, options: Options): TSESLint.RuleListener {\n const allowIn = options[0]?.allowIn ?? []\n if (allowIn.length > 0 && matchesGlob(context.filename, allowIn)) {\n return {}\n }\n\n const { sourceCode } = context\n\n return {\n Program() {\n for (const comment of sourceCode.getAllComments()) {\n const lines = comment.value.split(\"\\n\")\n\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index]\n if (line === undefined || !isDecorativeCommentLine(line)) continue\n\n const reportedLine = comment.loc.start.line + index\n const source = sourceCode.lines[reportedLine - 1] ?? \"\"\n\n context.report({\n loc: {\n start: { line: reportedLine, column: 0 },\n end: { line: reportedLine, column: source.length },\n },\n messageId: \"decorative\",\n })\n }\n }\n },\n }\n }\n}\n\nexport default new NoDecorativeCommentSeparators()\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport CONSTANTS from \"@/lib/constants\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = []\ntype MessageIds = \"emDash\"\n\n/**\n * Flags every em dash (—) character found anywhere in the source.\n */\nclass NoEmDash extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-em-dash\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"suggestion\",\n docs: {\n description: \"Disallow the em dash (—) character anywhere in the source.\",\n recommended: true,\n },\n schema: [],\n messages: {\n emDash: nitpick({\n problem: \"Found an em dash (—) character.\",\n why: \"Em dashes are typically introduced by AI-generated or auto-formatted text and are discouraged here.\",\n fix: \"Replace the em dash with a hyphen (-), a comma (,), or reword the sentence to avoid it.\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n const text = sourceCode.getText()\n\n return {\n Program() {\n // Scan the raw source so every em dash is caught, whether it appears\n // in code, strings, or comments\n for (let index = 0; index < text.length; index++) {\n if (text[index] !== CONSTANTS.EM_DASH) continue\n\n context.report({\n loc: {\n start: sourceCode.getLocFromIndex(index),\n end: sourceCode.getLocFromIndex(index + 1),\n },\n messageId: \"emDash\",\n })\n }\n },\n }\n }\n}\n\nexport default new NoEmDash()\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\"\n\n/**\n * Checks whether a comment is a JSDoc comment, i.e. a block comment that opens\n * with `/**`.\n * @param comment The comment to test.\n * @returns `true` if the comment is a JSDoc block comment.\n */\nexport function isJSDocComment(comment: TSESTree.Comment): boolean {\n return comment.type === \"Block\" && comment.value.startsWith(\"*\")\n}\n\n/**\n * Checks whether a node is immediately preceded by a JSDoc comment.\n * @param sourceCode The source code of the linted file.\n * @param node The node to inspect the leading comments of.\n * @returns `true` if the comment right before the node is a JSDoc comment.\n */\nexport function hasLeadingJSDoc(sourceCode: Readonly<TSESLint.SourceCode>, node: TSESTree.Node): boolean {\n const commentsBefore = sourceCode.getCommentsBefore(node)\n const closest = commentsBefore.at(-1)\n\n return closest !== undefined && isJSDocComment(closest)\n}\n\n/**\n * Checks whether a physical JSDoc line is blank, i.e. just a ` * ` with no\n * content after it.\n * @param line The physical source line to test.\n * @returns `true` if the line is an empty JSDoc line.\n */\nexport function isBlankJSDocLine(line: string): boolean {\n return /^\\s*\\*\\s*$/.test(line)\n}\n\n/**\n * Checks whether a physical JSDoc line holds a block tag, i.e. a ` * ` followed\n * by an `@tag` such as `@param` or `@returns`.\n * @param line The physical source line to test.\n * @returns `true` if the line starts a JSDoc tag.\n */\nexport function isJSDocTagLine(line: string): boolean {\n return /^\\s*\\*\\s*@/.test(line)\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { isBlankJSDocLine, isJSDocComment, isJSDocTagLine } from \"@/lib/utils/JSDoc\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = []\ntype MessageIds = \"blankBeforeTag\"\n\n/**\n * Flags blank lines that sit between a JSDoc description and its tags (or\n * between tags) and removes them so the tags follow on directly.\n */\nclass NoJSDocBlankBeforeTags extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-jsdoc-blank-before-tags\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"layout\",\n fixable: \"code\",\n docs: {\n description: \"Disallow blank lines before JSDoc tags such as `@param` or `@returns`.\",\n recommended: true,\n },\n schema: [],\n messages: {\n blankBeforeTag: nitpick({\n problem: \"There is a blank line before a JSDoc tag.\",\n why: \"Tags should follow the description directly; an empty line there is noise that inflates the comment.\",\n fix: \"Remove the blank line so the tag follows on directly.\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n\n return {\n Program() {\n for (const comment of sourceCode.getAllComments()) {\n if (!isJSDocComment(comment)) continue\n\n if (comment.loc.start.line === comment.loc.end.line) continue\n\n for (let line = comment.loc.start.line; line <= comment.loc.end.line; line++) {\n const text = sourceCode.lines[line - 1]\n if (text === undefined || !isBlankJSDocLine(text)) continue\n\n // Grow the run of consecutive blank lines\n let runEnd = line\n while (runEnd < comment.loc.end.line && isBlankJSDocLine(sourceCode.lines[runEnd] ?? \"\")) {\n runEnd++\n }\n\n // Only a blank run immediately before a tag is a problem\n const nextLine = sourceCode.lines[runEnd]\n if (nextLine !== undefined && isJSDocTagLine(nextLine)) {\n const from = sourceCode.getIndexFromLoc({ line, column: 0 })\n const to = sourceCode.getIndexFromLoc({ line: runEnd + 1, column: 0 })\n\n context.report({\n loc: {\n start: { line, column: 0 },\n end: { line: runEnd, column: text.length },\n },\n messageId: \"blankBeforeTag\",\n fix: fixer => fixer.removeRange([from, to]),\n })\n }\n\n line = runEnd\n }\n }\n },\n }\n }\n}\n\nexport default new NoJSDocBlankBeforeTags()\n","import CONSTANTS from \"@/lib/constants\"\n\n/**\n * Checks whether a character is a \"word\" character, i.e. something that can\n * appear inside an identifier or a number (letters, digits, underscore, dollar).\n * @param char The single character to test, or `undefined` (e.g. past the end\n * of a string).\n * @returns `true` if the character is a word character, `false` otherwise.\n */\nexport function isWordChar(char: string | undefined): boolean {\n return char !== undefined && CONSTANTS.WORD_CHAR.test(char)\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { isWordChar } from \"@/lib/utils/isWordChar\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = []\ntype MessageIds = \"period\"\n\n/**\n * Flags periods used as prose punctuation inside `//` line comments.\n *\n * Line comments should read as short, clear fragments rather than full\n * sentences, so periods are just noise. Dots that are part of a token, such as\n * `foo.bar`, `1.5`, `file.ts` or `.env`, and ellipses (`...`) are left alone.\n */\nclass NoLineCommentPeriod extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-line-comment-period\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"layout\",\n fixable: \"code\",\n docs: {\n description: \"Disallow prose periods in `//` line comments (code-reference dots and ellipses are allowed).\",\n recommended: true,\n },\n schema: [],\n messages: {\n period: nitpick({\n problem: \"This line comment contains a period.\",\n why: \"Line comments should be short, clear fragments, not full sentences, so periods are just noise, dots inside code references like `foo.bar` are allowed.\",\n fix: \"Remove the period and keep the comment terse.\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n\n return {\n Program() {\n for (const comment of sourceCode.getAllComments()) {\n if (comment.type !== \"Line\") continue\n\n const { value } = comment\n // The comment value starts right after the leading `//`\n const valueStart = comment.range[0] + 2\n\n for (let index = 0; index < value.length; index++) {\n if (value[index] !== \".\") continue\n\n // A run of consecutive dots is an ellipsis, leave it alone\n if (value[index + 1] === \".\") {\n while (value[index + 1] === \".\") index++\n continue\n }\n\n // A lone dot immediately followed by a word character is\n // part of a token (`foo.bar`, `1.5`, `.env`), not prose\n if (isWordChar(value[index + 1])) continue\n\n const at = valueStart + index\n\n context.report({\n loc: {\n start: sourceCode.getLocFromIndex(at),\n end: sourceCode.getLocFromIndex(at + 1),\n },\n messageId: \"period\",\n fix: fixer => fixer.removeRange([at, at + 1]),\n })\n }\n }\n },\n }\n }\n}\n\nexport default new NoLineCommentPeriod()\n","import type { TSESTree } from \"@typescript-eslint/utils\"\n\n/**\n * Checks whether an expression is nothing but a property access, i.e. a chain of\n * non-computed member accesses (with any `?.` or trailing `!`) that bottoms out\n * at an identifier or `this`, such as `auth.user!` or `menu.node.path`.\n *\n * Computed access (`arr[0]`), calls (`obj.method()`), and bare identifiers are\n * not property-access aliases.\n * @param node The expression to inspect.\n * @returns `true` if the expression is a plain property access.\n */\nexport function isPropertyAccessAlias(node: TSESTree.Expression): boolean {\n let expression: TSESTree.Node = node\n let sawMemberAccess = false\n\n while (true) {\n if (expression.type === \"ChainExpression\" || expression.type === \"TSNonNullExpression\") {\n expression = expression.expression\n continue\n }\n\n if (expression.type === \"MemberExpression\") {\n if (expression.computed) return false\n\n sawMemberAccess = true\n expression = expression.object\n continue\n }\n\n break\n }\n\n return sawMemberAccess && (expression.type === \"Identifier\" || expression.type === \"ThisExpression\")\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport { isPropertyAccessAlias } from \"@/lib/utils/aliases\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = []\ntype MessageIds = \"propertyAccessAlias\"\n\n/**\n * Flags a `const` whose entire value is a single property access, such as\n * `const user = auth.user!`, since it just renames a property and hides where\n * the value comes from, `let` is exempt, as it may be reassigned later.\n */\nclass NoPropertyAccessAlias extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-property-access-alias\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"suggestion\",\n docs: {\n description:\n \"Disallow a `const` whose whole value is a single property access, inline the expression instead.\",\n recommended: true,\n category: \"base\",\n },\n schema: [],\n messages: {\n propertyAccessAlias: nitpick({\n problem: \"`{{name}}` only aliases the property access `{{expression}}`.\",\n why: \"A variable that just renames a property hides where the value comes from when scanning the code\",\n fix: \"Remove it and use `{{expression}}` inline, or use `let` if it is reassigned later\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n\n return {\n VariableDeclarator(node) {\n if (node.parent.type !== \"VariableDeclaration\" || node.parent.kind !== \"const\") return\n\n // Exported bindings cannot be inlined away, so they are exempt\n if (node.parent.parent.type === \"ExportNamedDeclaration\") return\n\n if (node.id.type !== \"Identifier\" || node.init === null) return\n if (!isPropertyAccessAlias(node.init)) return\n\n context.report({\n node,\n messageId: \"propertyAccessAlias\",\n data: {\n name: node.id.name,\n expression: sourceCode.getText(node.init),\n },\n })\n },\n }\n }\n}\n\nexport default new NoPropertyAccessAlias()\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { isJSDocComment } from \"@/lib/utils/JSDoc\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = []\ntype MessageIds = \"singleLine\"\n\n/**\n * Flags JSDoc comments (`/**`) that are written on a single line and expands\n * them into the multi-line form:\n * ```\n * /** blabla *​/ -> /**\n * * blabla\n * *​/\n * ```\n */\nclass NoSingleLineJSDoc extends NitpickerRule<MessageIds, Options> {\n readonly name = \"no-single-line-jsdoc\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"layout\",\n fixable: \"code\",\n docs: {\n description: \"Require JSDoc comments to span multiple lines rather than sit on a single line.\",\n recommended: true,\n },\n schema: [],\n messages: {\n singleLine: nitpick({\n problem: \"This JSDoc comment is written on a single line.\",\n why: \"Multi-line JSDoc is easier to read, diff, and extend with additional tags, and is the house style.\",\n fix: \"Put the opening `/**`, the ` * ` content, and the closing `*/` each on their own line.\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n\n return {\n Program() {\n for (const comment of sourceCode.getAllComments()) {\n // Only JSDoc comments (`/**`) that fit on one line\n if (!isJSDocComment(comment)) continue\n if (comment.loc.start.line !== comment.loc.end.line) continue\n\n // Strip the leading `*` left over from `/**` and normalize\n const content = comment.value.replace(/^\\*/, \"\").trim()\n\n // An empty JSDoc (`/** */`) has nothing to expand onto its\n // own line, so it is left alone\n if (content.length === 0) continue\n\n context.report({\n loc: comment.loc,\n messageId: \"singleLine\",\n fix(fixer) {\n const indent = \" \".repeat(comment.loc.start.column)\n const expanded = `/**\\n${indent} * ${content}\\n${indent} */`\n return fixer.replaceTextRange(comment.range, expanded)\n },\n })\n }\n },\n }\n }\n}\n\nexport default new NoSingleLineJSDoc()\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * A framework Nitpicker ships a dedicated rule category and shared config for.\n */\nexport type Framework = \"adonisjs\" | \"react\"\n\n/**\n * The human-readable label for each framework, used in messages.\n */\nexport const FRAMEWORK_LABELS: Record<Framework, string> = {\n adonisjs: \"AdonisJS\",\n react: \"React\",\n}\n\n/**\n * Collects the module specifiers imported by a source file.\n * @param sourceCode The source code of the linted file.\n * @returns The list of imported module specifiers.\n */\nfunction importSources(sourceCode: Readonly<TSESLint.SourceCode>): string[] {\n const sources: string[] = []\n\n for (const statement of sourceCode.ast.body) {\n if (statement.type === \"ImportDeclaration\") {\n sources.push(String(statement.source.value))\n }\n }\n\n return sources\n}\n\n/**\n * Detects which supported frameworks a file appears to use, based on its imports\n * and file name.\n * @param sourceCode The source code of the linted file.\n * @param filename The path of the linted file.\n * @returns The set of frameworks detected in the file.\n */\nexport function detectFrameworks(sourceCode: Readonly<TSESLint.SourceCode>, filename: string): Set<Framework> {\n const detected = new Set<Framework>()\n const sources = importSources(sourceCode)\n\n if (sources.some(source => source.startsWith(\"@adonisjs/\") || CONSTANTS.ADONIS_SUBPATH.test(source))) {\n detected.add(\"adonisjs\")\n }\n\n const importsReact = sources.some(\n source => source === \"react\" || source.startsWith(\"react/\") || source === \"react-dom\",\n )\n if (importsReact || CONSTANTS.REACT_FILE.test(filename)) {\n detected.add(\"react\")\n }\n\n return detected\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport CONSTANTS from \"@/lib/constants\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { detectFrameworks, FRAMEWORK_LABELS, type Framework } from \"@/lib/utils/framework\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\n\ntype Options = [{ ignore: Framework[] }]\ntype MessageIds = \"missingConfig\"\n\n/**\n * Warns when a file uses a framework (AdonisJS, React) whose Nitpicker config is\n * not enabled, so its framework-specific conventions are silently going\n * unchecked. The warning disappears once the matching config is added (each one\n * sets a settings flag), or the rule can be turned off.\n */\nclass RequireFrameworkConfig extends NitpickerRule<MessageIds, Options> {\n readonly name = \"require-framework-config\"\n\n readonly defaultOptions: Options = [{ ignore: [] }]\n\n readonly meta = {\n type: \"suggestion\",\n docs: {\n description: \"Warn when a file uses a framework whose Nitpicker config is not enabled.\",\n recommended: true,\n category: \"base\",\n },\n schema: [\n {\n type: \"object\",\n properties: {\n ignore: {\n type: \"array\",\n items: {\n type: \"string\",\n enum: [\"adonisjs\", \"react\"],\n },\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n missingConfig: nitpick({\n problem: \"This file uses {{framework}} but the Nitpicker {{framework}} rules are not enabled.\",\n why: \"Framework rules only run when you opt into the matching config, so files like this one go unchecked\",\n fix: \"Add `nitpicker.configs.{{config}}` (scoped to these files) to your ESLint config, or turn off `nitpicker/require-framework-config`\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>, options: Options): TSESLint.RuleListener {\n const ignored = new Set(options[0]?.ignore ?? [])\n\n // Each framework config stamps `settings.nitpicker<framework> = true`\n const enabled = (context.settings[CONSTANTS.PLUGIN_NAME] ?? {}) as Partial<Record<Framework, boolean>>\n\n return {\n Program(node) {\n const detected = detectFrameworks(context.sourceCode, context.filename)\n\n for (const framework of detected) {\n if (ignored.has(framework) || enabled[framework]) continue\n\n context.report({\n node,\n messageId: \"missingConfig\",\n data: { framework: FRAMEWORK_LABELS[framework], config: framework },\n })\n }\n },\n }\n }\n}\n\nexport default new RequireFrameworkConfig()\n","import type { TSESTree } from \"@typescript-eslint/utils\"\n\n/**\n * Any node that introduces a callable function.\n */\nexport type FunctionNode = TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression\n\n/**\n * Resolves the node whose leading comments would document a function, walking\n * outwards through a variable declaration and/or an export statement.\n *\n * For `export const foo = () => {}` this is the `export` node, whereas for a\n * bare `function foo() {}` it is the declaration itself.\n * @param fn The function node to resolve from.\n * @returns The node a JSDoc comment would sit above.\n */\nexport function getDocumentableNode(fn: FunctionNode): TSESTree.Node {\n let node: TSESTree.Node = fn\n\n if (node.parent.type === \"VariableDeclarator\" && node.parent.parent.type === \"VariableDeclaration\") {\n node = node.parent.parent\n }\n\n if (node.parent.type === \"ExportNamedDeclaration\" || node.parent.type === \"ExportDefaultDeclaration\") {\n node = node.parent\n }\n\n return node\n}\n\n/**\n * Checks whether a node sits directly at the top level of the module.\n * @param node The node to test.\n * @returns `true` if the node's parent is the program root.\n */\nexport function isTopLevel(node: TSESTree.Node): boolean {\n return node.parent?.type === \"Program\"\n}\n\n/**\n * Resolves the declared name of a function, whether it comes from the function\n * itself or the variable it is assigned to.\n * @param fn The function node to name.\n * @returns The function name, or `undefined` if it is anonymous.\n */\nexport function getFunctionName(fn: FunctionNode): string | undefined {\n if ((fn.type === \"FunctionDeclaration\" || fn.type === \"FunctionExpression\") && fn.id) {\n return fn.id.name\n }\n\n if (fn.parent.type === \"VariableDeclarator\" && fn.parent.id.type === \"Identifier\") {\n return fn.parent.id.name\n }\n\n return undefined\n}\n","import type { TSESTree } from \"@typescript-eslint/utils\"\n\n/**\n * The AST node types that introduce a new function scope.\n */\nconst FUNCTION_NODE_TYPES = new Set<string>([\"FunctionDeclaration\", \"FunctionExpression\", \"ArrowFunctionExpression\"])\n\n/**\n * A lookup of AST node type to the property keys that hold its child nodes.\n */\ntype VisitorKeys = Record<string, readonly string[] | undefined>\n\n/**\n * Checks whether a name follows the `PascalCase` convention React uses to\n * distinguish component functions from plain functions and DOM tags.\n * @param name The function name to test.\n * @returns `true` if the name starts with an uppercase letter.\n */\nexport function isReactComponentName(name: string): boolean {\n return /^[A-Z]/.test(name)\n}\n\n/**\n * Checks whether an expression evaluates to JSX, following the branches a\n * component commonly returns through (ternaries, `&&`, comma sequences).\n * @param node The expression to inspect, if any.\n * @returns `true` if the expression can produce a JSX element or fragment.\n */\nexport function isJsxExpression(node: TSESTree.Expression | null | undefined): boolean {\n if (!node) return false\n\n switch (node.type) {\n case \"JSXElement\":\n case \"JSXFragment\":\n return true\n case \"ConditionalExpression\":\n return isJsxExpression(node.consequent) || isJsxExpression(node.alternate)\n case \"LogicalExpression\":\n return isJsxExpression(node.left) || isJsxExpression(node.right)\n case \"SequenceExpression\":\n return isJsxExpression(node.expressions.at(-1))\n default:\n return false\n }\n}\n\n/**\n * Recursively searches a subtree for a `return` that yields JSX, without\n * crossing into nested functions (whose returns belong to them, not us).\n * @param node The AST node to inspect.\n * @param visitorKeys The AST visitor keys, used to walk the subtree.\n */\nfunction subtreeReturnsJsx(node: TSESTree.Node, visitorKeys: VisitorKeys): boolean {\n if (node.type === \"ReturnStatement\") {\n return isJsxExpression(node.argument)\n }\n\n for (const key of visitorKeys[node.type] ?? []) {\n const value = (node as unknown as Record<string, unknown>)[key]\n const children = Array.isArray(value) ? value : [value]\n\n for (const child of children) {\n const childNode = child as TSESTree.Node | null | undefined\n if (!childNode || typeof childNode.type !== \"string\") continue\n\n // Nested functions own their own returns, so stop descending there\n if (FUNCTION_NODE_TYPES.has(childNode.type)) continue\n if (subtreeReturnsJsx(childNode, visitorKeys)) return true\n }\n }\n\n return false\n}\n\n/**\n * Checks whether a function returns JSX, i.e. whether it looks like it renders\n * a React element.\n * @param fn The function node to inspect.\n * @param visitorKeys The AST visitor keys, used to walk the function body.\n * @returns True if the function returns JSX.\n */\nexport function functionReturnsJsx(\n fn: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression,\n visitorKeys: VisitorKeys,\n): boolean {\n // An arrow with an expression body returns that expression directly\n if (fn.type === \"ArrowFunctionExpression\" && fn.body.type !== \"BlockStatement\") {\n return isJsxExpression(fn.body)\n }\n\n return subtreeReturnsJsx(fn.body, visitorKeys)\n}\n","import type { TSESLint, TSESTree } from \"@typescript-eslint/utils\"\nimport { NitpickerRule } from \"@/lib/rule\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport { type FunctionNode, getDocumentableNode, getFunctionName, isTopLevel } from \"@/lib/utils/functions\"\nimport { hasLeadingJSDoc } from \"@/lib/utils/JSDoc\"\nimport { nitpick } from \"@/lib/utils/nitpick\"\nimport { functionReturnsJsx, isReactComponentName } from \"@/lib/utils/react\"\n\ntype Options = []\ntype MessageIds = \"missingJSDoc\"\n\n/**\n * Requires a JSDoc comment on top-level functions, with React component\n * functions (`PascalCase` name returning JSX) being the sole exception.\n */\nclass RequireFunctionJSDoc extends NitpickerRule<MessageIds, Options> {\n readonly name = \"require-function-jsdoc\"\n\n readonly defaultOptions: Options = []\n\n readonly meta = {\n type: \"suggestion\",\n docs: {\n description: \"Require a JSDoc comment on top-level functions, except React component functions.\",\n recommended: true,\n },\n schema: [],\n messages: {\n missingJSDoc: nitpick({\n problem: \"The function `{{name}}` has no JSDoc comment.\",\n why: \"Top-level functions must document their purpose, parameters, and return value, React component functions are the only exception.\",\n fix: \"Add a `/** ... */` JSDoc block immediately above the function describing what it does.\",\n }),\n },\n } satisfies TSESLint.RuleMetaData<MessageIds, NitpickerRuleDocs, Options>\n\n create(context: Readonly<TSESLint.RuleContext<MessageIds, Options>>): TSESLint.RuleListener {\n const { sourceCode } = context\n\n const check = (fn: FunctionNode, reportNode: TSESTree.Node): void => {\n const name = getFunctionName(fn)\n // Anonymous functions (e.g `export default () => {}`) are skipped\n if (name === undefined) return\n\n const documentable = getDocumentableNode(fn)\n if (!isTopLevel(documentable)) return\n\n // React component functions are exempt from the JSDoc requirement\n if (isReactComponentName(name) && functionReturnsJsx(fn, sourceCode.visitorKeys)) return\n\n if (hasLeadingJSDoc(sourceCode, documentable)) return\n\n context.report({\n node: reportNode,\n messageId: \"missingJSDoc\",\n data: { name },\n })\n }\n\n return {\n FunctionDeclaration(node) {\n check(node, node.id ?? node)\n },\n VariableDeclarator(node) {\n const { init } = node\n if (!init) return\n if (init.type !== \"ArrowFunctionExpression\" && init.type !== \"FunctionExpression\") return\n\n check(init, node.id)\n },\n }\n }\n}\n\nexport default new RequireFunctionJSDoc()\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport type { NitpickerRuleDocs } from \"@/lib/utils/createRule\"\nimport noBritishEnglish from \"@/rules/base/noBritishEnglish\"\nimport noDecorativeCommentSeparators from \"@/rules/base/noDecorativeCommentSeparators\"\nimport noEmDash from \"@/rules/base/noEmDash\"\nimport noJSDocBlankBeforeTags from \"@/rules/base/noJSDocBlankBeforeTags\"\nimport noLineCommentPeriod from \"@/rules/base/noLineCommentPeriod\"\nimport noPropertyAccessAlias from \"@/rules/base/noPropertyAccessAlias\"\nimport noSingleLineJSDoc from \"@/rules/base/noSingleLineJSDoc\"\nimport requireFrameworkConfig from \"@/rules/base/requireFrameworkConfig\"\nimport requireFunctionJSDoc from \"@/rules/base/requireFunctionJSDoc\"\n\n/**\n * Every rule instance registered by the plugin.\n */\nconst ruleInstances = [\n noBritishEnglish,\n noDecorativeCommentSeparators,\n noEmDash,\n noJSDocBlankBeforeTags,\n noLineCommentPeriod,\n noPropertyAccessAlias,\n noSingleLineJSDoc,\n requireFrameworkConfig,\n requireFunctionJSDoc,\n]\n\n/**\n * The plugin's rules, keyed by name, as the plain modules ESLint consumes.\n */\nexport const rules = Object.fromEntries(ruleInstances.map(rule => [rule.name, rule.toRuleModule()])) as Record<\n string,\n TSESLint.RuleModule<string, readonly unknown[], NitpickerRuleDocs>\n>\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport CONSTANTS from \"@/lib/constants\"\nimport type { RuleCategory } from \"@/lib/utils/createRule\"\nimport { rules } from \"@/rules\"\n\n/**\n * Builds the rules record for a single category, each enabled as a warning,\n * rules with no explicit category are treated as `base`.\n * @param category The category to collect rules for.\n * @returns The flat-config rules record for that category.\n */\nexport function categoryRules(category: RuleCategory): TSESLint.FlatConfig.Rules {\n const enabled: TSESLint.FlatConfig.Rules = {}\n\n for (const [name, rule] of Object.entries(rules)) {\n if ((rule.meta.docs?.category ?? \"base\") === category) {\n enabled[`${CONSTANTS.PLUGIN_NAME}/${name}`] = \"warn\"\n }\n }\n\n return enabled\n}\n\n/**\n * Builds a rules record enabling every rule the plugin ships, as a warning.\n * @returns The flat-config rules record for all rules.\n */\nexport function allRules(): TSESLint.FlatConfig.Rules {\n const enabled: TSESLint.FlatConfig.Rules = {}\n\n for (const name of Object.keys(rules)) {\n enabled[`${CONSTANTS.PLUGIN_NAME}/${name}`] = \"warn\"\n }\n\n return enabled\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { allRules } from \"@/configs/helpers\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * Builds the `all` flat config: every rule the plugin ships, each enabled as a\n * warning, this is the maximally-pedantic Nitpicker experience. Both framework\n * settings flags are set, since enabling everything already opts into them.\n * @param plugin The plugin instance to register the rules against.\n * @returns The flat config object.\n */\nexport function all(plugin: TSESLint.FlatConfig.Plugin): TSESLint.FlatConfig.Config {\n return {\n name: `${CONSTANTS.PLUGIN_NAME}/all`,\n plugins: { [CONSTANTS.PLUGIN_NAME]: plugin },\n settings: { [CONSTANTS.PLUGIN_NAME]: { adonisjs: true, react: true } },\n rules: allRules(),\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { categoryRules } from \"@/configs/helpers\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * Builds the `base` flat config: the universal rules that apply to every file\n * regardless of framework.\n * @param plugin The plugin instance to register the rules against.\n * @returns The flat config object.\n */\nexport function base(plugin: TSESLint.FlatConfig.Plugin): TSESLint.FlatConfig.Config {\n return {\n name: `${CONSTANTS.PLUGIN_NAME}/base`,\n plugins: { [CONSTANTS.PLUGIN_NAME]: plugin },\n rules: categoryRules(\"base\"),\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { base } from \"@/configs/rulesets/base\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * Builds the `recommended` flat config: the sensible default for any project,\n * which is the universal `base` ruleset.\n * @param plugin The plugin instance to register the rules against.\n * @returns The flat config object.\n */\nexport function recommended(plugin: TSESLint.FlatConfig.Plugin): TSESLint.FlatConfig.Config {\n return {\n ...base(plugin),\n name: `${CONSTANTS.PLUGIN_NAME}/recommended`,\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { categoryRules } from \"@/configs/helpers\"\nimport CONSTANTS from \"@/lib/constants\"\n\n// AdonisJS route files where banner separators are the clearest way to group routes\nconst ROUTE_FILE_GLOBS = [\"**/start/routes.ts\", \"**/start/routes/**/*.ts\"]\n\n/**\n * Builds the `adonisjs` flat config: AdonisJS-specific rules, plus a settings\n * flag so `require-framework-config` knows AdonisJS is opted into for these files.\n * It also permits decorative banners in route files, where they aid readability.\n * @param plugin The plugin instance to register the rules against.\n * @returns The flat config object.\n */\nexport function adonisjs(plugin: TSESLint.FlatConfig.Plugin): TSESLint.FlatConfig.Config {\n return {\n name: `${CONSTANTS.PLUGIN_NAME}/adonisjs`,\n plugins: { [CONSTANTS.PLUGIN_NAME]: plugin },\n settings: { [CONSTANTS.PLUGIN_NAME]: { adonisjs: true } },\n rules: {\n ...categoryRules(\"adonisjs\"),\n [`${CONSTANTS.PLUGIN_NAME}/no-decorative-comment-separators`]: [\"warn\", { allowIn: ROUTE_FILE_GLOBS }],\n },\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { categoryRules } from \"@/configs/helpers\"\nimport CONSTANTS from \"@/lib/constants\"\n\n/**\n * Builds the `react` flat config: React-specific rules, plus a settings flag so\n * `require-framework-config` knows React is opted into for these files.\n * @param plugin The plugin instance to register the rules against.\n * @returns The flat config object.\n */\nexport function react(plugin: TSESLint.FlatConfig.Plugin): TSESLint.FlatConfig.Config {\n return {\n name: `${CONSTANTS.PLUGIN_NAME}/react`,\n plugins: { [CONSTANTS.PLUGIN_NAME]: plugin },\n settings: { [CONSTANTS.PLUGIN_NAME]: { react: true } },\n rules: categoryRules(\"react\"),\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { all } from \"@/configs/presets/all\"\nimport { recommended } from \"@/configs/presets/recommended\"\nimport { adonisjs } from \"@/configs/rulesets/adonisjs\"\nimport { base } from \"@/configs/rulesets/base\"\nimport { react } from \"@/configs/rulesets/react\"\n\n/**\n * Builds all shared configs bundled with the plugin.\n *\n * Note: Configs are built from a factory rather than declared statically\n * because each one needs a reference to the plugin instance it belongs to.\n * @param plugin The plugin instance to register the rules against.\n * @returns A record of config name to flat config object.\n */\nexport function buildConfigs(plugin: TSESLint.FlatConfig.Plugin): Record<string, TSESLint.FlatConfig.Config> {\n return {\n base: base(plugin),\n recommended: recommended(plugin),\n adonisjs: adonisjs(plugin),\n react: react(plugin),\n all: all(plugin),\n }\n}\n","{\n \"name\": \"@alien_intelligence/eslint-plugin-nitpicker\",\n \"productName\": \"Nitpicker\",\n \"version\": \"0.1.0\",\n \"description\": \"A hyper-pedantic ESLint plugin that flags every stylistic and semantic nit, with AI-friendly fix context.\",\n \"author\": \"Alien <contact@alien.club> (https://www.alien.club/)\",\n \"license\": \"MIT\",\n \"private\": false,\n \"packageManager\": \"npm@11.4.2\",\n \"type\": \"module\",\n \"keywords\": [\n \"alien\",\n \"eslint\",\n \"eslintplugin\",\n \"eslint-plugin\",\n \"nitpicker\",\n \"linting\",\n \"code-style\",\n \"ai\"\n ],\n \"homepage\": \"https://github.com/the-alien-club/eslint-plugin-nitpicker\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/the-alien-club/eslint-plugin-nitpicker.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/the-alien-club/eslint-plugin-nitpicker/issues\"\n },\n \"publishConfig\": {\n \"registry\": \"https://registry.npmjs.org/\",\n \"access\": \"public\",\n \"tag\": \"latest\"\n },\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"default\": \"./dist/index.js\"\n }\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"lint\": \"biome check\",\n \"lint:fix\": \"biome check --fix && biome format --write\",\n \"lint:nitpicker\": \"eslint \\\"src/**/*.ts\\\" \\\"tests/**/*.ts\\\"\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"peerDependencies\": {\n \"eslint\": \">=9.0.0\"\n },\n \"dependencies\": {\n \"@typescript-eslint/utils\": \"^8.18.0\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^2.5.2\",\n \"@types/node\": \"^24.12.2\",\n \"@typescript-eslint/parser\": \"^8.64.0\",\n \"eslint\": \"^9.17.0\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^5.8.3\",\n \"vitest\": \"^4.1.5\"\n }\n}\n","import type { TSESLint } from \"@typescript-eslint/utils\"\nimport { buildConfigs } from \"@/configs\"\nimport CONSTANTS from \"@/lib/constants\"\nimport { rules } from \"@/rules\"\n\n// Import the version from package.json to include in the plugin metadata\nimport { version } from \"../package.json\"\n\n/**\n * Main declaration of the `@alien_intelligence/eslint-plugin-nitpicker` plugin.\n */\nconst plugin: TSESLint.FlatConfig.Plugin = {\n meta: {\n name: `eslint-plugin-${CONSTANTS.PLUGIN_NAME}`,\n version,\n },\n rules,\n configs: {},\n}\n\n// Configs reference the plugin, so they are attached after it is created\nplugin.configs = buildConfigs(plugin)\n\nexport default plugin\n"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@alien_intelligence/eslint-plugin-nitpicker",
3
+ "productName": "Nitpicker",
4
+ "version": "0.1.0",
5
+ "description": "A hyper-pedantic ESLint plugin that flags every stylistic and semantic nit, with AI-friendly fix context.",
6
+ "author": "Alien <contact@alien.club> (https://www.alien.club/)",
7
+ "license": "MIT",
8
+ "private": false,
9
+ "packageManager": "npm@11.4.2",
10
+ "type": "module",
11
+ "keywords": [
12
+ "alien",
13
+ "eslint",
14
+ "eslintplugin",
15
+ "eslint-plugin",
16
+ "nitpicker",
17
+ "linting",
18
+ "code-style",
19
+ "ai"
20
+ ],
21
+ "homepage": "https://github.com/the-alien-club/eslint-plugin-nitpicker",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/the-alien-club/eslint-plugin-nitpicker.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/the-alien-club/eslint-plugin-nitpicker/issues"
28
+ },
29
+ "publishConfig": {
30
+ "registry": "https://registry.npmjs.org/",
31
+ "access": "public",
32
+ "tag": "latest"
33
+ },
34
+ "engines": {
35
+ "node": ">=20.0.0"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "main": "./dist/index.js",
43
+ "module": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "exports": {
46
+ ".": {
47
+ "types": "./dist/index.d.ts",
48
+ "import": "./dist/index.js",
49
+ "default": "./dist/index.js"
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "lint": "biome check",
55
+ "lint:fix": "biome check --fix && biome format --write",
56
+ "lint:nitpicker": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest"
60
+ },
61
+ "peerDependencies": {
62
+ "eslint": ">=9.0.0"
63
+ },
64
+ "dependencies": {
65
+ "@typescript-eslint/utils": "^8.18.0"
66
+ },
67
+ "devDependencies": {
68
+ "@biomejs/biome": "^2.5.2",
69
+ "@types/node": "^24.12.2",
70
+ "@typescript-eslint/parser": "^8.64.0",
71
+ "eslint": "^9.17.0",
72
+ "tsup": "^8.5.1",
73
+ "typescript": "^5.8.3",
74
+ "vitest": "^4.1.5"
75
+ }
76
+ }