diff --git a/api/lint.ts b/api/lint.ts index eb644fd00..2cbb693f4 100644 --- a/api/lint.ts +++ b/api/lint.ts @@ -11,6 +11,8 @@ const ruleNames = Object.keys(plugin.rules) as RuleName[] const isRuleName = (rule: string): rule is RuleName => ruleNames.includes(rule as RuleName) +const isExcludedRule = (rule: RuleName) => ['quine'].includes(rule) + const lintLocal = ( code: string, rule: Rule, @@ -48,9 +50,12 @@ const lintServerless = async (request: VercelRequest, res: VercelResponse) => { // if (!parsed.success) return res.status(400).send({error: 'Invalid payload'}) const { code, options, rule } = parsed - if (!isRuleName(rule)) { + if (!isRuleName(rule)) return res.status(400).send({ error: 'invalid rule name' }) - } + + if (isExcludedRule(rule)) + return res.status(400).send({ error: 'rule not supported' }) + const lintResults = lintLocal(code, rule, options) return res.status(200).send(lintResults) } diff --git a/docs/rules/align.md b/docs/rules/align.md index 41df14901..14dc5c571 100644 --- a/docs/rules/align.md +++ b/docs/rules/align.md @@ -1,5 +1,5 @@ --- -title: center +title: align description: Align text elegantly --- @@ -11,7 +11,7 @@ import {ruleName, presetConfigs, initialText} from '../../src/sample-code/align' > "Typography is two-dimensional architecture, based on experience and > imagination, and guided by rules and readability." — Hermann Zapf -# Enforce elegant text alignment (`align`) +# align 💼 This rule is enabled in the following [configs](/configs/): 🌐 `all`, ✅ `recommended`. diff --git a/docs/rules/index.md b/docs/rules/index.md index f71c1f1d7..10022e801 100644 --- a/docs/rules/index.md +++ b/docs/rules/index.md @@ -33,6 +33,7 @@ description: ESLint Plugin Ninja list of rules | [prefer-emoji](/rules/prefer-emoji) | require variables and properties to be named using emojis | 🔧 | | [prefer-npm](/rules/prefer-npm) | require from npm instead of using JS builtins | 🔧 | | [prefer-tab](/rules/prefer-tab) | require word separators to be tabs, not spaces | 🔧 | +| [quine](/rules/quine) | enforce quine | | | [yes](/rules/yes) | enforce nothing | | diff --git a/docs/rules/quine.md b/docs/rules/quine.md new file mode 100644 index 000000000..df79a668d --- /dev/null +++ b/docs/rules/quine.md @@ -0,0 +1,41 @@ +--- +title: quine +description: Enforce quines +--- + + + +> "This sentence contains thirty-six characters." + +# Enforce quines (`quine`) + +💼 This rule is enabled in the 🌐 `all` [config](/configs/). + + + +## 💡 Examples + +::: code-group + + +```js [Center] +// ✅ Correct +($=_=>`($=${$})()`)() +``` + + +```js [Right] +// ✅ Correct +( function quine() {console.log("( " + quine.toString() + " )()")} )() +``` + +::: + +## 🔧 Config + +```js +{ rules: { 'ninja/quine': 2 } } +``` diff --git a/index.ts b/index.ts index 7d610761f..08dbc685d 100644 --- a/index.ts +++ b/index.ts @@ -15,6 +15,7 @@ import lottery, { RULE_NAME as lotteryName } from './rules/lottery' import noRush, { RULE_NAME as noRushName } from './rules/no-rush' import noWoof, { RULE_NAME as noWoofName } from './rules/no-woof' import noXkcd, { RULE_NAME as noXkcdName } from './rules/no-xkcd' +import quine, { RULE_NAME as quineName } from './rules/quine' import align, { RULE_NAME as alignName } from './rules/align' import noCi, { RULE_NAME as noCiName } from './rules/no-ci' import noTs, { RULE_NAME as noTsName } from './rules/no-ts' @@ -45,6 +46,7 @@ const rules = { [preferEmojiName]: preferEmoji, [preferNpmName]: preferNpm, [preferTabName]: preferTab, + [quineName]: quine, [yesName]: yes, } as const @@ -67,13 +69,19 @@ const recommendedRules = [ preferEmojiName, preferNpmName, yesName, -] as const +] as const satisfies (keyof typeof rules)[] const all: Record = {} const recommended: Record = {} -for (const n of Object.keys(rules)) all[`ninja/${n}`] = [2, {}] -for (const n of recommendedRules) recommended[`ninja/${n}`] = [2, {}] +const excludedDangerousRules = [quineName] + +for (const n of Object.keys(rules) as (keyof typeof rules)[]) + if (!excludedDangerousRules.includes(n)) all[`ninja/${n}`] = [2, {}] + +for (const n of recommendedRules) + if (excludedDangerousRules.includes(n)) throw new Error(`Rule ${n} should not be included in recommended rules`) + else recommended[`ninja/${n}`] = [2, {}] recommended['ninja/no-rush'] = [1, { delay: 1 }] recommended['ninja/lottery'] = [2, { probability: 0.9 }] diff --git a/package.json b/package.json index fb1da6635..3131e41d9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "eslint-plugin-ninja", "description": "Optimized ESLint Rules for Elite Coders", - "version": "0.0.8", + "version": "0.0.9", "homepage": "https://www.dont.ninja", "repository": { "type": "git", diff --git a/readme.md b/readme.md index dad6885a1..5f9e8d841 100644 --- a/readme.md +++ b/readme.md @@ -68,6 +68,7 @@ npm i -D eslint-plugin-ninja | [prefer-emoji](https://www.dont.ninja/rules/prefer-emoji) | require variables and properties to be named using emojis | 🔧 | | [prefer-npm](https://www.dont.ninja/rules/prefer-npm) | require from npm instead of using JS builtins | 🔧 | | [prefer-tab](https://www.dont.ninja/rules/prefer-tab) | require word separators to be tabs, not spaces | 🔧 | +| [quine](https://www.dont.ninja/rules/quine) | enforce quine | | | [yes](https://www.dont.ninja/rules/yes) | enforce nothing | | diff --git a/rules/quine.ts b/rules/quine.ts new file mode 100644 index 000000000..b8a922284 --- /dev/null +++ b/rules/quine.ts @@ -0,0 +1,39 @@ +import type { RuleContext, RuleListener } from '../utils/eslint-types/Rule' + +import { createEslintRule } from '../utils/create-eslint-rule' + +type MESSAGE_ID = 'not a quine' + +type Options = [] + +export const RULE_NAME = 'quine' +type Context = RuleContext + +export default createEslintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'enforce quine', + }, + schema: [{ type: 'object' }], + messages: { + 'not a quine': 'not a quine', + }, + }, + defaultOptions: [], + create: (context: Context): RuleListener => { + const sourceCode = context.getSourceCode() + const text = sourceCode?.text || '' + const processCode = (code: string) => { + // eslint-disable-next-line no-eval + const evalResult = eval(code) + if (code !== evalResult) + context.report({ + messageId: 'not a quine', + loc: { column: 0, line: 1 }, + }) + } + return { Program: () => processCode(text) } + }, +}) diff --git a/src/sample-code/quine.ts b/src/sample-code/quine.ts new file mode 100644 index 000000000..7a4f1cad4 --- /dev/null +++ b/src/sample-code/quine.ts @@ -0,0 +1,8 @@ +import type { PresetConfig } from './presets' + +export const presetConfigs = [] satisfies PresetConfig[] + +export const ruleName = 'quine' + +// eslint-disable-next-line no-template-curly-in-string +export const initialText = '($=_=>`($=${$})()`)()'