routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
import path from 'node:path';
|
||||
import packageJson from '../../package.json' with {type: 'json'};
|
||||
|
||||
const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn';
|
||||
|
||||
export default function getDocumentationUrl(filename) {
|
||||
const ruleName = path.basename(filename, '.js');
|
||||
return `${repoUrl}/blob/v${packageJson.version}/docs/rules/${ruleName}.md`;
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornCreate} from './to-eslint-create.js';
|
||||
@import {UnicornRule} from './to-eslint-rule.js';
|
||||
@import {UnicornContext} from './unicorn-context.js';
|
||||
@import {TSESTree as Estree} from '@typescript-eslint/types';
|
||||
*/
|
||||
|
||||
export {default as toEslintRules} from './to-eslint-rules.js';
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import createUnicornContext from './unicorn-context.js';
|
||||
import UnicornListeners from './unicorn-listeners.js';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornContext} from './unicorn-context.js';
|
||||
@import {EslintListers, ListenerType, EslintListener} from './to-eslint-listener.js'
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {ESLint.Rule.RuleModule['create']} EslintCreate
|
||||
@typedef {(context: UnicornContext) => void} UnicornCreate
|
||||
*/
|
||||
|
||||
// `checkVueTemplate` function will wrap `create` function, there is no need to wrap twice
|
||||
const wrappedFunctions = new Set();
|
||||
const markFunctionWrapped = create => {
|
||||
wrappedFunctions.add(create);
|
||||
return create;
|
||||
};
|
||||
|
||||
/**
|
||||
Convert Unicorn style of `create` to ESLint style
|
||||
|
||||
@param {UnicornCreate} unicornCreate
|
||||
@returns {EslintCreate}
|
||||
*/
|
||||
function toEslintCreate(unicornCreate) {
|
||||
if (wrappedFunctions.has(unicornCreate)) {
|
||||
return unicornCreate;
|
||||
}
|
||||
|
||||
return eslintContext => {
|
||||
const unicornListeners = new UnicornListeners(eslintContext);
|
||||
const unicornContext = createUnicornContext(eslintContext, unicornListeners);
|
||||
|
||||
const result = unicornCreate(unicornContext);
|
||||
|
||||
assert.equal(result, undefined, `[${eslintContext.id}] Rule \`create\` function should return \`undefined\`, please use \`context.on()\` instead of return listeners.`);
|
||||
|
||||
const eslintListeners = unicornListeners.toEslintListeners();
|
||||
|
||||
return eslintListeners;
|
||||
};
|
||||
}
|
||||
|
||||
export default toEslintCreate;
|
||||
export {markFunctionWrapped};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import {iterateFixOrProblems} from './utilities.js';
|
||||
import toEslintProblem from './to-eslint-problem.js';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornContext} from './unicorn-context.js'
|
||||
@import {UnicornProblems} from './to-eslint-problem.js'
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {ESLint.Rule.RuleListener} EslintListers
|
||||
@typedef {keyof EslintListers} ListenerType
|
||||
@typedef {EslintListers[ListenerType]} EslintListener
|
||||
@typedef {(...listenerArguments: Parameters<EslintListener>) => UnicornProblems} UnicornRuleListen
|
||||
*/
|
||||
|
||||
/**
|
||||
@param {UnicornContext} context
|
||||
@param {UnicornRuleListen} listener
|
||||
@returns {Listener}
|
||||
*/
|
||||
function toEslintListener(context, listener) {
|
||||
// Listener arguments can be `codePath, node` or `node`
|
||||
|
||||
/**
|
||||
@type {UnicornRuleListen}
|
||||
*/
|
||||
return (...listenerArguments) => {
|
||||
const unicornProblems = listener(...listenerArguments);
|
||||
|
||||
for (const unicornProblem of iterateFixOrProblems(unicornProblems)) {
|
||||
if (unicornProblem) {
|
||||
const eslintProblem = toEslintProblem(unicornProblem);
|
||||
context.report(eslintProblem);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default toEslintListener;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import toEslintFixer from './to-eslint-rule-fixer.js';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {Parameters<ESLint.Rule.RuleContext['report']>[0]} EslintProblem
|
||||
@typedef {EslintProblem} UnicornProblem
|
||||
@typedef {EslintProblem | undefined | EslintProblem[] | IterableIterator<EslintProblem>} UnicornProblems
|
||||
*/
|
||||
|
||||
/**
|
||||
@param {UnicornProblem} unicornProblem
|
||||
@returns {EslintProblem}
|
||||
*/
|
||||
function toEslintProblem(unicornProblem) {
|
||||
const eslintProblem = {...unicornProblem};
|
||||
|
||||
if (unicornProblem.fix) {
|
||||
eslintProblem.fix = toEslintFixer(unicornProblem.fix);
|
||||
}
|
||||
|
||||
if (Array.isArray(unicornProblem.suggest)) {
|
||||
eslintProblem.suggest = unicornProblem.suggest.map(unicornSuggest => ({
|
||||
...unicornSuggest,
|
||||
fix: toEslintFixer(unicornSuggest.fix),
|
||||
data: {
|
||||
...unicornProblem.data,
|
||||
...unicornSuggest.data,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return eslintProblem;
|
||||
}
|
||||
|
||||
export default toEslintProblem;
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import {iterateFixOrProblems} from './utilities.js';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
*/
|
||||
|
||||
class FixAbortError extends Error {
|
||||
name = 'FixAbortError';
|
||||
}
|
||||
|
||||
const fixOptions = {
|
||||
abort() {
|
||||
throw new FixAbortError('Fix aborted.');
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@typedef {ESLint.Rule.ReportFixer | undefined} EslintReportFixer
|
||||
@typedef {EslintReportFixer | IterableIterator<EslintReportFixer>} UnicornReportFixer
|
||||
@typedef {(fixer: ESLint.Rule.RuleFixer, options: typeof fixOptions) => UnicornReportFixer} UnicornRuleFixer
|
||||
*/
|
||||
|
||||
/**
|
||||
Convert Unicorn style fix function to ESLint style fix function
|
||||
|
||||
@param {UnicornRuleFixer} fix
|
||||
@returns {ESLint.Rule.RuleFixer}
|
||||
*/
|
||||
function toEslintRuleFixer(fix) {
|
||||
/** @param {UnicornReportFixer} fixer */
|
||||
return fixer => {
|
||||
const unicornReport = fix(fixer, fixOptions);
|
||||
|
||||
const eslintReport = iterateFixOrProblems(unicornReport);
|
||||
|
||||
try {
|
||||
return [...eslintReport];
|
||||
} catch (error) {
|
||||
if (error instanceof FixAbortError) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default toEslintRuleFixer;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import getDocumentationUrl from '../utils/get-documentation-url.js';
|
||||
import toEslintCreate from './to-eslint-create.js';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornCreate} from './to-eslint-create.js';
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {ESLint.Rule.RuleModule & {
|
||||
create: UnicornCreate
|
||||
}} UnicornRule
|
||||
*/
|
||||
|
||||
/**
|
||||
Convert Unicorn rule to Eslint rule
|
||||
|
||||
@param {string} ruleId
|
||||
@param {UnicornRule} unicornRule
|
||||
@returns {ESLint.Rule.RuleModule}
|
||||
*/
|
||||
function toEslintRule(ruleId, unicornRule) {
|
||||
return {
|
||||
meta: {
|
||||
// If there is are, options add `[]` so ESLint can validate that no data is passed to the rule.
|
||||
// https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-schema.md
|
||||
schema: [],
|
||||
...unicornRule.meta,
|
||||
docs: {
|
||||
...unicornRule.meta.docs,
|
||||
url: getDocumentationUrl(ruleId),
|
||||
},
|
||||
},
|
||||
create: toEslintCreate(unicornRule.create),
|
||||
};
|
||||
}
|
||||
|
||||
export default toEslintRule;
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import toEslintRule from './to-eslint-rule.js';
|
||||
|
||||
function toEslintRules(rules) {
|
||||
return Object.fromEntries(Object.entries(rules).map(([ruleId, rule]) => [
|
||||
ruleId,
|
||||
toEslintRule(ruleId, rule),
|
||||
]));
|
||||
}
|
||||
|
||||
export default toEslintRules;
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornListeners, ListenerType, Listener} from './to-eslint-create.js'
|
||||
*/
|
||||
|
||||
/**
|
||||
@typedef {(type: ListenerType | ListenerType[], listener: Listener) => ReturnType<Listener>} UnicornRuleListen
|
||||
@typedef {ESLint.Rule.RuleContext & {
|
||||
on: UnicornRuleListen
|
||||
onExit: UnicornRuleListen
|
||||
}} UnicornContext
|
||||
*/
|
||||
|
||||
/**
|
||||
Create a better `Context` object with `on` and `onExit` method to add listeners
|
||||
|
||||
@param {ESLint.Rule.RuleContext} eslintContext
|
||||
@param {UnicornListeners} listeners
|
||||
@returns {UnicornContext}
|
||||
*/
|
||||
function createUnicornContext(eslintContext, listeners) {
|
||||
/** @type {UnicornContext} */
|
||||
const context = new Proxy(eslintContext, {
|
||||
get(target, property, receiver) {
|
||||
if (property === 'on' || property === 'onExit') {
|
||||
return listeners[property].bind(listeners);
|
||||
}
|
||||
|
||||
return Reflect.get(target, property, receiver);
|
||||
},
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export default createUnicornContext;
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import toEslintListener from './to-eslint-listener.js';
|
||||
|
||||
/**
|
||||
@import {EslintListers, ListenerType, Listener} from './to-eslint-create.js'
|
||||
*/
|
||||
|
||||
class UnicornListeners {
|
||||
#context;
|
||||
#listeners = new Map();
|
||||
|
||||
constructor(context) {
|
||||
this.#context = context;
|
||||
}
|
||||
|
||||
#addEventListener(selectors, listener) {
|
||||
const listeners = this.#listeners;
|
||||
for (const selector of selectors) {
|
||||
if (listeners.has(selector)) {
|
||||
listeners.get(selector).push(listener);
|
||||
} else {
|
||||
listeners.set(selector, [listener]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@param {ListenerType | ListenerType[]} selectorOrSelectors
|
||||
@param {Listener} listener
|
||||
*/
|
||||
on(selectorOrSelectors, listener) {
|
||||
const selectors = Array.isArray(selectorOrSelectors) ? selectorOrSelectors : [selectorOrSelectors];
|
||||
this.#addEventListener(selectors, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@param {ListenerType | ListenerType[]} selectorOrSelectors
|
||||
@param {Listener} listener
|
||||
*/
|
||||
onExit(selectorOrSelectors, listener) {
|
||||
const selectors = Array.isArray(selectorOrSelectors) ? selectorOrSelectors : [selectorOrSelectors];
|
||||
this.#addEventListener(selectors.map(selector => `${selector}:exit`), listener);
|
||||
}
|
||||
|
||||
/**
|
||||
@returns {EslintListers}
|
||||
*/
|
||||
toEslintListeners() {
|
||||
const eslintListeners = {};
|
||||
|
||||
for (const [selector, listeners] of this.#listeners) {
|
||||
eslintListeners[selector] = toEslintListener(
|
||||
this.#context,
|
||||
function * (...listenerArguments) {
|
||||
for (const listener of listeners) {
|
||||
yield listener(...listenerArguments);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return eslintListeners;
|
||||
}
|
||||
}
|
||||
|
||||
export default UnicornListeners;
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
const isIterable = object => typeof object?.[Symbol.iterator] === 'function';
|
||||
|
||||
/**
|
||||
@import * as ESLint from 'eslint';
|
||||
@import {UnicornReportFixer} from './to-eslint-rule-fixer.js';
|
||||
@import {UnicornProblems, UnicornProblem} from './to-eslint-problem.js';
|
||||
*/
|
||||
|
||||
/**
|
||||
Iterate ESLint fix or ESLint problem
|
||||
|
||||
@template {UnicornReportFixer | UnicornProblems} ValueType
|
||||
|
||||
@param {ValueType} value
|
||||
@returns {IterableIterator<ValueType extends UnicornReportFixer ? ESLint.Rule.Fix : UnicornProblem>}
|
||||
*/
|
||||
export function * iterateFixOrProblems(value) {
|
||||
if (!isIterable(value)) {
|
||||
yield value;
|
||||
return;
|
||||
}
|
||||
|
||||
for (const element of value) {
|
||||
yield * iterateFixOrProblems(element);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user