first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import header from "./src/rules/header.js";
|
||||
|
||||
export default {
|
||||
rules: { header },
|
||||
rulesConfig: { header: 0 },
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@ufb/eslint-plugin-header",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./index.js"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export function commentParser(text) {
|
||||
text = text.trim();
|
||||
|
||||
if (text.substr(0, 2) === '//') {
|
||||
return [
|
||||
'line',
|
||||
text.split(/\r?\n/).map(function (line) {
|
||||
return line.substr(2);
|
||||
}),
|
||||
];
|
||||
} else if (text.substr(0, 2) === '/*' && text.substr(-2) === '*/') {
|
||||
return ['block', text.substring(2, text.length - 2)];
|
||||
} else {
|
||||
throw new Error(
|
||||
'Could not parse comment file: the file must contain either just line comments (//) or a single block comment (/* ... */)',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
|
||||
// @ts-ignore
|
||||
function commentParser(text) {
|
||||
text = text.trim();
|
||||
|
||||
if (text.substr(0, 2) === "//") {
|
||||
return [
|
||||
"line",
|
||||
// @ts-ignore
|
||||
text.split(/\r?\n/).map(function (line) {
|
||||
return line.substr(2);
|
||||
}),
|
||||
];
|
||||
} else if (text.substr(0, 2) === "/*" && text.substr(-2) === "*/") {
|
||||
return ["block", text.substring(2, text.length - 2)];
|
||||
} else {
|
||||
throw new Error(
|
||||
"Could not parse comment file: the file must contain either just line comments (//) or a single block comment (/* ... */)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function isPattern(object) {
|
||||
return (
|
||||
typeof object === "object" &&
|
||||
Object.prototype.hasOwnProperty.call(object, "pattern")
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function match(actual, expected) {
|
||||
if (expected.test) {
|
||||
return expected.test(actual);
|
||||
} else {
|
||||
return expected === actual;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function excludeShebangs(comments) {
|
||||
// @ts-ignore
|
||||
return comments.filter(function (comment) {
|
||||
return comment.type !== "Shebang";
|
||||
});
|
||||
}
|
||||
|
||||
// Returns either the first block comment or the first set of line comments that
|
||||
// are ONLY separated by a single newline. Note that this does not actually
|
||||
// check if they are at the start of the file since that is already checked by
|
||||
// hasHeader().
|
||||
// @ts-ignore
|
||||
function getLeadingComments(context, node) {
|
||||
var all = excludeShebangs(
|
||||
context
|
||||
.getSourceCode()
|
||||
.getAllComments(node.body.length ? node.body[0] : node)
|
||||
);
|
||||
if (all[0].type.toLowerCase() === "block") {
|
||||
return [all[0]];
|
||||
}
|
||||
for (var i = 1; i < all.length; ++i) {
|
||||
var txt = context
|
||||
.getSourceCode()
|
||||
.getText()
|
||||
.slice(all[i - 1].range[1], all[i].range[0]);
|
||||
if (!txt.match(/^(\r\n|\r|\n)$/)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return all.slice(0, i);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function genCommentBody(commentType, textArray, eol, numNewlines) {
|
||||
var eols = eol.repeat(numNewlines);
|
||||
if (commentType === "block") {
|
||||
return "/*" + textArray.join(eol) + "*/" + eols;
|
||||
} else {
|
||||
return "//" + textArray.join(eol + "//") + eols;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function genCommentsRange(context, comments, eol) {
|
||||
var start = comments[0].range[0];
|
||||
var end = comments.slice(-1)[0].range[1];
|
||||
if (context.getSourceCode().text[end] === eol) {
|
||||
end += eol.length;
|
||||
}
|
||||
return [start, end];
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function genPrependFixer(commentType, node, headerLines, eol, numNewlines) {
|
||||
// @ts-ignore
|
||||
return function (fixer) {
|
||||
return fixer.insertTextBefore(
|
||||
node,
|
||||
genCommentBody(commentType, headerLines, eol, numNewlines)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function genReplaceFixer(
|
||||
// @ts-ignore
|
||||
commentType,
|
||||
// @ts-ignore
|
||||
context,
|
||||
// @ts-ignore
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
headerLines,
|
||||
// @ts-ignore
|
||||
eol,
|
||||
// @ts-ignore
|
||||
numNewlines
|
||||
) {
|
||||
// @ts-ignore
|
||||
return function (fixer) {
|
||||
return fixer.replaceTextRange(
|
||||
genCommentsRange(context, leadingComments, eol),
|
||||
genCommentBody(commentType, headerLines, eol, numNewlines)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function findSettings(options) {
|
||||
var lastOption = options.length > 0 ? options[options.length - 1] : null;
|
||||
if (
|
||||
typeof lastOption === "object" &&
|
||||
!Array.isArray(lastOption) &&
|
||||
lastOption !== null &&
|
||||
!Object.prototype.hasOwnProperty.call(lastOption, "pattern")
|
||||
) {
|
||||
return lastOption;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function getEOL(options) {
|
||||
var settings = findSettings(options);
|
||||
if (settings && settings.lineEndings === "unix") {
|
||||
return "\n";
|
||||
}
|
||||
if (settings && settings.lineEndings === "windows") {
|
||||
return "\r\n";
|
||||
}
|
||||
return os.EOL;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function hasHeader(src) {
|
||||
if (src.substr(0, 2) === "#!") {
|
||||
var m = src.match(/(\r\n|\r|\n)/);
|
||||
if (m) {
|
||||
src = src.slice(m.index + m[0].length);
|
||||
}
|
||||
}
|
||||
return src.substr(0, 2) === "/*" || src.substr(0, 2) === "//";
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function matchesLineEndings(src, num) {
|
||||
for (var j = 0; j < num; ++j) {
|
||||
var m = src.match(/^(\r\n|\r|\n)/);
|
||||
if (m) {
|
||||
src = src.slice(m.index + m[0].length);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: "layout",
|
||||
fixable: "whitespace",
|
||||
schema: {
|
||||
$ref: "#/definitions/options",
|
||||
definitions: {
|
||||
commentType: {
|
||||
type: "string",
|
||||
enum: ["block", "line"],
|
||||
},
|
||||
line: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
pattern: {
|
||||
type: "string",
|
||||
},
|
||||
template: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
required: ["pattern"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
headerLines: {
|
||||
anyOf: [
|
||||
{
|
||||
$ref: "#/definitions/line",
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
items: {
|
||||
$ref: "#/definitions/line",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
numNewlines: {
|
||||
type: "integer",
|
||||
minimum: 0,
|
||||
},
|
||||
settings: {
|
||||
type: "object",
|
||||
properties: {
|
||||
lineEndings: {
|
||||
type: "string",
|
||||
enum: ["unix", "windows"],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
options: {
|
||||
anyOf: [
|
||||
{
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 2,
|
||||
items: [{ type: "string" }, { $ref: "#/definitions/settings" }],
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
minItems: 2,
|
||||
maxItems: 3,
|
||||
items: [
|
||||
{ $ref: "#/definitions/commentType" },
|
||||
{ $ref: "#/definitions/headerLines" },
|
||||
{ $ref: "#/definitions/settings" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "array",
|
||||
minItems: 3,
|
||||
maxItems: 4,
|
||||
items: [
|
||||
{ $ref: "#/definitions/commentType" },
|
||||
{ $ref: "#/definitions/headerLines" },
|
||||
{ $ref: "#/definitions/numNewlines" },
|
||||
{ $ref: "#/definitions/settings" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// @ts-ignore
|
||||
create: function (context) {
|
||||
var options = context.options;
|
||||
var numNewlines = options.length > 2 ? options[2] : 1;
|
||||
var eol = getEOL(options);
|
||||
|
||||
// If just one option then read comment from file
|
||||
if (
|
||||
options.length === 1 ||
|
||||
(options.length === 2 && findSettings(options))
|
||||
) {
|
||||
var text = fs.readFileSync(context.options[0], "utf8");
|
||||
options = commentParser(text);
|
||||
}
|
||||
|
||||
var commentType = options[0].toLowerCase();
|
||||
// @ts-ignore
|
||||
var headerLines,
|
||||
// @ts-ignore
|
||||
fixLines = [];
|
||||
// If any of the lines are regular expressions, then we can't
|
||||
// automatically fix them. We set this to true below once we
|
||||
// ensure none of the lines are of type RegExp
|
||||
var canFix = false;
|
||||
if (Array.isArray(options[1])) {
|
||||
canFix = true;
|
||||
headerLines = options[1].map(function (line) {
|
||||
var isRegex = isPattern(line);
|
||||
// Can only fix regex option if a template is also provided
|
||||
if (isRegex && !line.template) {
|
||||
canFix = false;
|
||||
}
|
||||
fixLines.push(line.template || line);
|
||||
return isRegex ? new RegExp(line.pattern) : line;
|
||||
});
|
||||
} else if (isPattern(options[1])) {
|
||||
var line = options[1];
|
||||
headerLines = [new RegExp(line.pattern)];
|
||||
fixLines.push(line.template || line);
|
||||
// Same as above for regex and template
|
||||
canFix = !!line.template;
|
||||
} else {
|
||||
canFix = true;
|
||||
headerLines = options[1].split(/\r?\n/);
|
||||
fixLines = headerLines;
|
||||
}
|
||||
|
||||
return {
|
||||
// @ts-ignore
|
||||
Program: function (node) {
|
||||
if (!hasHeader(context.getSourceCode().getText())) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "missing header",
|
||||
// @ts-ignore
|
||||
fix: genPrependFixer(commentType, node, fixLines, eol, numNewlines),
|
||||
});
|
||||
} else {
|
||||
var leadingComments = getLeadingComments(context, node);
|
||||
|
||||
if (!leadingComments.length) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "missing header",
|
||||
fix: canFix
|
||||
? // @ts-ignore
|
||||
genPrependFixer(commentType, node, fixLines, eol, numNewlines)
|
||||
: null,
|
||||
});
|
||||
} else if (leadingComments[0].type.toLowerCase() !== commentType) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "header should be a {{commentType}} comment",
|
||||
data: {
|
||||
commentType: commentType,
|
||||
},
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
} else {
|
||||
if (commentType === "line") {
|
||||
if (leadingComments.length < headerLines.length) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "incorrect header",
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < headerLines.length; i++) {
|
||||
// @ts-ignore
|
||||
if (!match(leadingComments[i].value, headerLines[i])) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "incorrect header",
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var postLineHeader = context
|
||||
.getSourceCode()
|
||||
.text.substr(
|
||||
leadingComments[headerLines.length - 1].range[1],
|
||||
numNewlines * 2
|
||||
);
|
||||
if (!matchesLineEndings(postLineHeader, numNewlines)) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "no newline after header",
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// if block comment pattern has more than 1 line, we also split the comment
|
||||
var leadingLines = [leadingComments[0].value];
|
||||
if (headerLines.length > 1) {
|
||||
leadingLines = leadingComments[0].value.split(/\r?\n/);
|
||||
}
|
||||
|
||||
var hasError = false;
|
||||
if (leadingLines.length > headerLines.length) {
|
||||
hasError = true;
|
||||
}
|
||||
for (i = 0; !hasError && i < headerLines.length; i++) {
|
||||
// @ts-ignore
|
||||
if (!match(leadingLines[i], headerLines[i])) {
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
if (canFix && headerLines.length > 1) {
|
||||
// @ts-ignore
|
||||
fixLines = [fixLines.join(eol)];
|
||||
}
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "incorrect header",
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
} else {
|
||||
var postBlockHeader = context
|
||||
.getSourceCode()
|
||||
.text.substr(leadingComments[0].range[1], numNewlines * 2);
|
||||
if (!matchesLineEndings(postBlockHeader, numNewlines)) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
message: "no newline after header",
|
||||
fix: canFix
|
||||
? genReplaceFixer(
|
||||
commentType,
|
||||
context,
|
||||
leadingComments,
|
||||
// @ts-ignore
|
||||
fixLines,
|
||||
eol,
|
||||
numNewlines
|
||||
)
|
||||
: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/// <reference types="./types.d.ts" />
|
||||
|
||||
import * as path from 'node:path';
|
||||
import { includeIgnoreFile } from '@eslint/compat';
|
||||
import eslint from '@eslint/js';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import turboPlugin from 'eslint-plugin-turbo';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
import headerPlugin from '@ufb/eslint-plugin-header';
|
||||
|
||||
/**
|
||||
* All packages that leverage t3-env should use this rule
|
||||
*/
|
||||
export const restrictEnvAccess = tseslint.config(
|
||||
{ ignores: ['**/env.ts'] },
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'no-restricted-properties': [
|
||||
'error',
|
||||
{
|
||||
object: 'process',
|
||||
property: 'env',
|
||||
message:
|
||||
"Use `import { env } from '~/env'` instead to ensure validated types.",
|
||||
},
|
||||
],
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
name: 'process',
|
||||
importNames: ['env'],
|
||||
message:
|
||||
"Use `import { env } from '~/env'` instead to ensure validated types.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export default tseslint.config(
|
||||
// Ignore files not tracked by VCS and any config files
|
||||
includeIgnoreFile(path.join(import.meta.dirname, '../../.gitignore')),
|
||||
{ ignores: ['**/*.config.*'] },
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
|
||||
plugins: {
|
||||
import: importPlugin,
|
||||
turbo: turboPlugin,
|
||||
header: headerPlugin,
|
||||
},
|
||||
extends: [
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
rules: {
|
||||
...turboPlugin.configs.recommended.rules,
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'warn',
|
||||
{ prefer: 'type-imports', fixStyle: 'separate-type-imports' },
|
||||
],
|
||||
'@typescript-eslint/no-misused-promises': [
|
||||
2,
|
||||
{ checksVoidReturn: { attributes: false } },
|
||||
],
|
||||
'@typescript-eslint/no-unnecessary-condition': [
|
||||
'error',
|
||||
{
|
||||
allowConstantLoopConditions: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
'header/header': [
|
||||
'error',
|
||||
'block',
|
||||
[
|
||||
'*',
|
||||
' * Copyright 2025 LY Corporation',
|
||||
' *',
|
||||
' * LY Corporation licenses this file to you under the Apache License,',
|
||||
' * version 2.0 (the "License"); you may not use this file except in compliance',
|
||||
' * with the License. You may obtain a copy of the License at:',
|
||||
' *',
|
||||
' * https://www.apache.org/licenses/LICENSE-2.0',
|
||||
' *',
|
||||
' * Unless required by applicable law or agreed to in writing, software',
|
||||
' * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT',
|
||||
' * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the',
|
||||
' * License for the specific language governing permissions and limitations',
|
||||
' * under the License.',
|
||||
' ',
|
||||
],
|
||||
1,
|
||||
],
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'off',
|
||||
'@typescript-eslint/no-base-to-string': 'off',
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
linterOptions: { reportUnusedDisableDirectives: true },
|
||||
languageOptions: { parserOptions: { projectService: true } },
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/** @type {Awaited<import('typescript-eslint').Config>} */
|
||||
/** @type {Awaited<import('typescript-eslint').Config>} */
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import nextPlugin from '@next/eslint-plugin-next';
|
||||
|
||||
/** @type {Awaited<import('typescript-eslint').Config>} */
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
plugins: {
|
||||
'@next/next': nextPlugin,
|
||||
},
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
// TypeError: context.getAncestors is not a function
|
||||
'@next/next/no-duplicate-head': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'@next/next/no-page-custom-font': 'off',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@ufb/eslint-config",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./base": "./base.js",
|
||||
"./nestjs": "./nestjs.js",
|
||||
"./nextjs": "./nextjs.js",
|
||||
"./react": "./react.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .cache .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/compat": "^2.0.3",
|
||||
"@next/eslint-plugin-next": "^16.2.1",
|
||||
"@ufb/eslint-plugin-header": "workspace:*",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-compiler": "beta",
|
||||
"eslint-plugin-react-hooks": "^6.1.1",
|
||||
"eslint-plugin-turbo": "^2.8.3",
|
||||
"typescript-eslint": "^8.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ufb/prettier-config": "workspace:*",
|
||||
"@ufb/tsconfig": "workspace:*",
|
||||
"eslint": "catalog:",
|
||||
"prettier": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"prettier": "@ufb/prettier-config"
|
||||
}
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
import reactPlugin from 'eslint-plugin-react';
|
||||
import compilerPlugin from 'eslint-plugin-react-compiler';
|
||||
import hooksPlugin from 'eslint-plugin-react-hooks';
|
||||
|
||||
/** @type {Awaited<import('typescript-eslint').Config>} */
|
||||
export default [
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
plugins: {
|
||||
react: reactPlugin,
|
||||
'react-compiler': compilerPlugin,
|
||||
'react-hooks': hooksPlugin,
|
||||
},
|
||||
rules: {
|
||||
...reactPlugin.configs['jsx-runtime'].rules,
|
||||
...hooksPlugin.configs.recommended.rules,
|
||||
'react/prop-types': 'off',
|
||||
'react/jsx-key': 'error',
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
'react/display-name': 'off',
|
||||
'react-hooks/rules-of-hooks': 'off',
|
||||
'react-compiler/react-compiler': 'error',
|
||||
},
|
||||
languageOptions: {
|
||||
globals: {
|
||||
React: 'writable',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@ufb/tsconfig/base.json",
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Since the ecosystem hasn't fully migrated to ESLint's new FlatConfig system yet,
|
||||
* we "need" to type some of the plugins manually :(
|
||||
*/
|
||||
|
||||
declare module 'eslint-plugin-import' {
|
||||
import type { Linter, Rule } from 'eslint';
|
||||
|
||||
export const configs: {
|
||||
recommended: { rules: Linter.RulesRecord };
|
||||
};
|
||||
export const rules: Record<string, Rule.RuleModule>;
|
||||
}
|
||||
|
||||
declare module 'eslint-plugin-react' {
|
||||
import type { Linter, Rule } from 'eslint';
|
||||
|
||||
export const configs: {
|
||||
recommended: { rules: Linter.RulesRecord };
|
||||
all: { rules: Linter.RulesRecord };
|
||||
'jsx-runtime': { rules: Linter.RulesRecord };
|
||||
};
|
||||
export const rules: Record<string, Rule.RuleModule>;
|
||||
}
|
||||
|
||||
declare module 'eslint-plugin-react-compiler' {}
|
||||
|
||||
declare module 'eslint-plugin-react-hooks' {
|
||||
import type { Linter, Rule } from 'eslint';
|
||||
|
||||
export const configs: {
|
||||
recommended: {
|
||||
rules: {
|
||||
'rules-of-hooks': Linter.RuleEntry;
|
||||
'exhaustive-deps': Linter.RuleEntry;
|
||||
};
|
||||
};
|
||||
};
|
||||
export const rules: Record<string, Rule.RuleModule>;
|
||||
}
|
||||
|
||||
declare module '@next/eslint-plugin-next' {
|
||||
import type { Linter, Rule } from 'eslint';
|
||||
|
||||
export const configs: {
|
||||
recommended: { rules: Linter.RulesRecord };
|
||||
'core-web-vitals': { rules: Linter.RulesRecord };
|
||||
};
|
||||
export const rules: Record<string, Rule.RuleModule>;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@ufb/github"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
name: "Setup and install"
|
||||
description: "Common setup steps for Actions"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: "pnpm"
|
||||
|
||||
- shell: bash
|
||||
run: pnpm add -g turbo
|
||||
|
||||
- shell: bash
|
||||
run: pnpm install
|
||||
@@ -0,0 +1,43 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
/** @typedef {import("prettier").Config} PrettierConfig */
|
||||
/** @typedef {import("prettier-plugin-tailwindcss").PluginOptions} TailwindConfig */
|
||||
/** @typedef {import("@ianvs/prettier-plugin-sort-imports").PluginConfig} SortImportsConfig */
|
||||
|
||||
/** @type { PrettierConfig | SortImportsConfig | TailwindConfig } */
|
||||
const config = {
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
experimentalTernaries: true,
|
||||
endOfLine: 'auto',
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
plugins: [
|
||||
'@ianvs/prettier-plugin-sort-imports',
|
||||
'prettier-plugin-tailwindcss',
|
||||
],
|
||||
tailwindConfig: fileURLToPath(
|
||||
new URL('../../packages/ufb-tailwindcss/index.js', import.meta.url),
|
||||
),
|
||||
tailwindFunctions: ['cn', 'cva'],
|
||||
importOrder: [
|
||||
'^(react/(.*)$)|^(react$)',
|
||||
'^(next/(.*)$)|^(next$)',
|
||||
'<THIRD_PARTY_MODULES>',
|
||||
'',
|
||||
'^@ufb/(.*)$',
|
||||
'',
|
||||
'^(@/shared/(.*))$|^(@/shared$)',
|
||||
'^(@/entities/(.*)$)|^(@/entities$)',
|
||||
'^(@/features/(.*)$)|^(@/features$)',
|
||||
'^(@/widgets/(.*)$)|^(@/widgets$)',
|
||||
'',
|
||||
'^@/',
|
||||
'^[../]',
|
||||
'^[./]',
|
||||
],
|
||||
importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'],
|
||||
importOrderTypeScriptVersion: '4.4.0',
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@ufb/prettier-config",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .cache .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.1",
|
||||
"prettier": "catalog:",
|
||||
"prettier-plugin-tailwindcss": "^0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.0",
|
||||
"@ufb/tsconfig": "workspace:*",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"prettier": "@ufb/prettier-config"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@ufb/tsconfig/base.json",
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"incremental": true,
|
||||
"disableSourceOfProjectReferenceRedirect": true,
|
||||
"tsBuildInfoFile": "${configDir}/.cache/tsbuildinfo.json",
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"checkJs": true,
|
||||
"module": "Preserve",
|
||||
"moduleResolution": "Bundler",
|
||||
"noEmit": true
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist", ".next"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "${configDir}/dist"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2023",
|
||||
"sourceMap": true,
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@ufb/tsconfig",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"*.json"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user