mirror of
https://github.com/HeyPuter/puter.git
synced 2025-12-19 03:30:47 -06:00
lint: configure eslint not operator spacing
The styleguide for Puter's backend expects spaces after top-level not operators inside conditions. This commit adds an AI-generated eslint plugin and, since it conflicts with the existing space-unary-ops plugin from stylistic, also adds a wrapped version of space-unary-ops that ignores top-level not operators so that this plugin will work as expected.
This commit is contained in:
@@ -4,7 +4,9 @@ import tseslintPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import tseslintParser from '@typescript-eslint/parser';
|
||||
import { defineConfig } from 'eslint/config';
|
||||
import globals from 'globals';
|
||||
import controlStructureSpacing from './control-structure-spacing.js';
|
||||
import bangSpaceIf from './eslint/bang-space-if.js';
|
||||
import controlStructureSpacing from './eslint/control-structure-spacing.js';
|
||||
import spaceUnaryOpsWithException from './eslint/space-unary-ops-with-exception.js';
|
||||
|
||||
const rules = {
|
||||
'no-unused-vars': ['error', {
|
||||
@@ -44,11 +46,12 @@ const rules = {
|
||||
'@stylistic/space-infix-ops': ['error'],
|
||||
'no-undef': 'error',
|
||||
'custom/control-structure-spacing': 'error',
|
||||
'custom/bang-space-if': 'error',
|
||||
'@stylistic/no-trailing-spaces': 'error',
|
||||
'@stylistic/space-before-blocks': ['error', 'always'],
|
||||
'prefer-template': 'error',
|
||||
'@stylistic/no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
|
||||
'@stylistic/space-unary-ops': ['error', { words: true, nonwords: false }],
|
||||
'custom/space-unary-ops-with-exception': ['error', { words: true, nonwords: false }],
|
||||
'@stylistic/no-multi-spaces': ['error', { exceptions: { 'VariableDeclarator': true } }],
|
||||
};
|
||||
|
||||
@@ -124,7 +127,11 @@ export default defineConfig([
|
||||
plugins: {
|
||||
js,
|
||||
'@stylistic': stylistic,
|
||||
custom: { rules: { 'control-structure-spacing': controlStructureSpacing } },
|
||||
custom: { rules: {
|
||||
'control-structure-spacing': controlStructureSpacing,
|
||||
'bang-space-if': bangSpaceIf,
|
||||
'space-unary-ops-with-exception': spaceUnaryOpsWithException,
|
||||
} },
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
69
eslint/bang-space-if.js
Normal file
69
eslint/bang-space-if.js
Normal file
@@ -0,0 +1,69 @@
|
||||
// eslint-plugin-bang-space-if/index.js
|
||||
'use strict';
|
||||
|
||||
/** @type {import('eslint').ESLint.Plugin} */
|
||||
export default {
|
||||
meta: {
|
||||
type: 'layout',
|
||||
docs: {
|
||||
description:
|
||||
"Require a space after a top-level '!' in an if(...) condition (e.g., `if ( ! entry )`).",
|
||||
recommended: false,
|
||||
},
|
||||
fixable: 'whitespace',
|
||||
schema: [], // no options
|
||||
},
|
||||
create (context) {
|
||||
const source = context.getSourceCode();
|
||||
|
||||
// Unwrap ParenthesizedExpression layers, if any
|
||||
function unwrapParens (node) {
|
||||
let n = node;
|
||||
// ESLint/ESTree: ParenthesizedExpression is supported by espree
|
||||
while ( n && n.type === 'ParenthesizedExpression' ) {
|
||||
n = n.expression;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
return {
|
||||
IfStatement (ifNode) {
|
||||
const testRaw = ifNode.test;
|
||||
if ( !testRaw ) return;
|
||||
|
||||
const test = unwrapParens(testRaw);
|
||||
if ( !test || test.type !== 'UnaryExpression' || test.operator !== '!' ) {
|
||||
return; // only top-level `!` expressions
|
||||
}
|
||||
|
||||
// Ignore boolean-cast `!!x` cases to avoid producing `! !x`
|
||||
if ( test.argument && test.argument.type === 'UnaryExpression' && test.argument.operator === '!' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab operator and argument tokens
|
||||
const opToken = source.getFirstToken(test); // should be '!'
|
||||
const argToken = source.getTokenAfter(opToken, { includeComments: false });
|
||||
if ( !opToken || !argToken ) return;
|
||||
|
||||
// Compute current whitespace between '!' and the argument
|
||||
const between = source.text.slice(opToken.range[1], argToken.range[0]);
|
||||
|
||||
// We want exactly one space
|
||||
if ( between === ' ' ) return;
|
||||
|
||||
context.report({
|
||||
node: test,
|
||||
loc: {
|
||||
start: opToken.loc.end,
|
||||
end: argToken.loc.start,
|
||||
},
|
||||
message: "Expected a single space after top-level '!' in if(...) condition.",
|
||||
fix (fixer) {
|
||||
return fixer.replaceTextRange([opToken.range[1], argToken.range[0]], ' ');
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};;;;
|
||||
37
eslint/space-unary-ops-with-exception.js
Normal file
37
eslint/space-unary-ops-with-exception.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import ruleComposer from 'eslint-rule-composer';
|
||||
|
||||
// Adjust this require to match the package you use for the rule.
|
||||
// For eslint-stylistic v2+ the package is "@stylistic/eslint-plugin"
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
const baseRule = stylistic.rules['space-unary-ops'];
|
||||
|
||||
// unwrap nested parentheses
|
||||
function unwrapParens (node) {
|
||||
let n = node;
|
||||
while ( n && n.type === 'ParenthesizedExpression' ) n = n.expression;
|
||||
return n;
|
||||
}
|
||||
|
||||
function isTopLevelBangInIfTest (node) {
|
||||
if ( !node || node.type !== 'UnaryExpression' || node.operator !== '!' ) return false;
|
||||
|
||||
// Walk up through ancestors manually using .parent (safe in ESLint)
|
||||
let current = node;
|
||||
let parent = current.parent;
|
||||
|
||||
// Skip ParenthesizedExpression layers
|
||||
while ( parent && parent.type === 'ParenthesizedExpression' ) {
|
||||
current = parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return parent && parent.type === 'IfStatement' && unwrapParens(parent.test) === node;
|
||||
}
|
||||
|
||||
// Filter out ONLY the reports for top-level ! inside if(...) condition
|
||||
export default ruleComposer.filterReports(baseRule, (problem, context) => {
|
||||
const { node } = problem;
|
||||
// If this particular report is about a top-level ! in an if(...) test,
|
||||
// suppress it. Otherwise, keep the original report.
|
||||
return !isTopLevelBangInIfTest(node, context);
|
||||
});
|
||||
51
package-lock.json
generated
51
package-lock.json
generated
@@ -7,7 +7,6 @@
|
||||
"": {
|
||||
"name": "puter.com",
|
||||
"version": "2.5.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"workspaces": [
|
||||
"src/*",
|
||||
@@ -15,6 +14,7 @@
|
||||
"experiments/js-parse-and-output"
|
||||
],
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.68.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.879.0",
|
||||
"@aws-sdk/client-sns": "^3.907.0",
|
||||
"@google/genai": "^1.19.0",
|
||||
@@ -43,6 +43,7 @@
|
||||
"clean-css": "^5.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-rule-composer": "^0.3.0",
|
||||
"express": "^4.18.2",
|
||||
"globals": "^15.15.0",
|
||||
"html-entities": "^2.3.3",
|
||||
@@ -69,12 +70,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.56.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.56.0.tgz",
|
||||
"integrity": "sha512-SLCB8M8+VMg1cpCucnA1XWHGWqVSZtIWzmOdDOEu3eTFZMB+A0sGZ1ESO5MHDnqrNTXz3safMrWx9x4rMZSOqA==",
|
||||
"version": "0.68.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.68.0.tgz",
|
||||
"integrity": "sha512-SMYAmbbiprG8k1EjEPMTwaTqssDT7Ae+jxcR5kWXiqTlbwMR2AthXtscEVWOHkRfyAV5+y3PFYTJRNa3OJWIEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"anthropic-ai-sdk": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/sha256-browser": {
|
||||
@@ -1098,7 +1110,6 @@
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -10677,6 +10688,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-rule-composer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
|
||||
"integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
@@ -13146,6 +13167,19 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-to-ts": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
|
||||
"integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"ts-algebra": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
@@ -17978,6 +18012,12 @@
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-algebra": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
|
||||
"integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
|
||||
@@ -19449,7 +19489,6 @@
|
||||
"version": "2.5.1",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.56.0",
|
||||
"@aws-sdk/client-polly": "^3.622.0",
|
||||
"@aws-sdk/client-textract": "^3.621.0",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"clean-css": "^5.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-rule-composer": "^0.3.0",
|
||||
"express": "^4.18.2",
|
||||
"globals": "^15.15.0",
|
||||
"html-entities": "^2.3.3",
|
||||
@@ -62,10 +63,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.68.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.879.0",
|
||||
"@aws-sdk/client-sns": "^3.907.0",
|
||||
"@google/genai": "^1.19.0",
|
||||
"@anthropic-ai/sdk": "^0.68.0",
|
||||
"@heyputer/putility": "^1.0.2",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@stylistic/eslint-plugin-js": "^4.4.1",
|
||||
@@ -89,4 +90,4 @@
|
||||
"engines": {
|
||||
"node": ">=20.19.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user