[repo] move to Oxlint

This commit is contained in:
Yangshun
2026-03-20 14:56:41 +08:00
parent 84a4ad84d8
commit fa1ec6c7ec
19 changed files with 415 additions and 1916 deletions

90
.oxlintrc.json Normal file
View File

@@ -0,0 +1,90 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "react"],
"categories": {
"correctness": "error"
},
"settings": {
"next": {
"rootDir": ["apps/portal/"]
}
},
"ignorePatterns": [
".next/**",
".turbo/**",
".cache/**",
"dist/**",
"dist-ssr/**",
"coverage/**",
"public/dist/**",
"server/dist/**",
"apps/portal/build/**",
"apps/portal/out/**",
"apps/portal/prisma/**",
"apps/website/.docusaurus/**",
"apps/website/.cache-loader/**",
"apps/website/experimental/**"
],
"rules": {
"camelcase": ["error", { "properties": "never", "ignoreDestructuring": true }],
"capitalized-comments": [
"error",
"always",
{ "ignoreConsecutiveComments": true }
],
"curly": "error",
"eqeqeq": ["error", "smart"],
"func-names": ["error", "as-needed"],
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"guard-for-in": "error",
"init-declarations": "error",
"no-console": ["error", { "allow": ["warn", "error", "info"] }],
"no-else-return": ["error", { "allowElseIf": false }],
"no-lonely-if": "error",
"no-shadow": "off",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"operator-assignment": "error",
"prefer-const": "error",
"prefer-destructuring": ["error", { "object": true }],
"radix": "error",
"react/button-has-type": "error",
"react/display-name": "off",
"react/exhaustive-deps": "off",
"react/jsx-boolean-value": ["error", "always"],
"react/jsx-curly-brace-presence": [
"error",
{ "props": "never", "children": "never" }
],
"react/jsx-no-useless-fragment": "error",
"react/no-array-index-key": "error",
"react/no-unescaped-entities": "off",
"react/react-in-jsx-scope": "off",
"react/void-dom-elements-no-children": "error",
"typescript/array-type": [
"error",
{ "default": "generic", "readonly": "generic" }
],
"typescript/ban-ts-comment": "off",
"typescript/consistent-generic-constructors": ["error", "constructor"],
"typescript/consistent-indexed-object-style": ["error", "record"],
"typescript/consistent-type-definitions": ["error", "type"],
"typescript/consistent-type-imports": "error",
"typescript/dot-notation": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-explicit-any": "off",
"typescript/no-for-in-array": "error",
"typescript/prefer-optional-chain": "error",
"typescript/require-array-sort-compare": "error",
"typescript/restrict-plus-operands": "error"
},
"overrides": [
{
"files": ["apps/portal/**/*.{js,jsx,ts,tsx}"],
"plugins": ["typescript", "react", "nextjs"],
"rules": {
"nextjs/no-html-link-for-pages": "off",
"nextjs/no-img-element": "off"
}
}
]
}

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["oxc.oxc-vscode"]
}

View File

@@ -7,14 +7,13 @@
"editor.formatOnSaveMode": "file",
"javascript.format.enable": true,
"json.format.enable": true,
"eslint.format.enable": false,
"css.format.enable": true,
"css.format.newlineBetweenRules": true,
"css.format.newlineBetweenSelectors": true,
"css.format.preserveNewLines": true,
"typescript.format.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll.oxc": "always"
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"

View File

@@ -1,11 +0,0 @@
module.exports = {
root: true,
extends: ['tih'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
rules: {
'@typescript-eslint/ban-ts-comment': 0,
},
};

View File

@@ -1,4 +1,4 @@
import { env } from './src/env/server.mjs';
import './src/env/server.mjs';
/**
* Don't be scared of the generics here.

View File

@@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"lint": "oxlint next.config.mjs src",
"tsc": "tsc",
"postinstall": "prisma generate",
"seed": "ts-node prisma/seed.ts",

View File

@@ -19,7 +19,7 @@ if (_serverEnv.success === false) {
/**
* Validate that server-side environment variables are not exposed to the client.
*/
for (let key of Object.keys(_serverEnv.data)) {
for (const key of Object.keys(_serverEnv.data)) {
if (key.startsWith('NEXT_PUBLIC_')) {
console.warn('❌ You are exposing a server-side env-variable:', key);

View File

@@ -1,9 +1,9 @@
import type { JobType } from '@prisma/client';
export type Profile = {
analysis: ProfileAnalysis?;
background: Background?;
editToken: string?;
analysis?: ProfileAnalysis | null;
background?: Background | null;
editToken?: string | null;
id: string;
isEditable: boolean;
isSaved: boolean;
@@ -20,15 +20,15 @@ export type Background = {
};
export type Experience = {
company: OffersCompany?;
durationInMonths: number?;
company?: OffersCompany | null;
durationInMonths?: number | null;
id: string;
jobType: JobType?;
level: string?;
location: Location?;
monthlySalary: Valuation?;
title: string?;
totalCompensation: Valuation?;
jobType?: JobType | null;
level?: string | null;
location?: Location | null;
monthlySalary?: Valuation | null;
title?: string | null;
totalCompensation?: Valuation | null;
};
export type OffersCompany = {
@@ -50,12 +50,12 @@ export type Valuation = {
};
export type Education = {
endDate: Date?;
field: string?;
endDate?: Date | null;
field?: string | null;
id: string;
school: string?;
startDate: Date?;
type: string?;
school?: string | null;
startDate?: Date | null;
type?: string | null;
};
export type SpecificYoe = {
@@ -87,16 +87,16 @@ export type ProfileOffer = {
location: Location;
monthYearReceived: Date;
negotiationStrategy: string;
offersFullTime: FullTime?;
offersIntern: Intern?;
offersFullTime?: FullTime | null;
offersIntern?: Intern | null;
};
export type FullTime = {
baseSalary: Valuation?;
bonus: Valuation?;
baseSalary?: Valuation | null;
bonus?: Valuation | null;
id: string;
level: string;
stocks: Valuation?;
stocks?: Valuation | null;
title: string;
totalCompensation: Valuation;
};
@@ -113,17 +113,17 @@ export type Reply = {
createdAt: Date;
id: string;
message: string;
replies: Array<Reply>?;
replyingToId: string?;
user: User?;
replies?: Array<Reply> | null;
replyingToId?: string | null;
user?: User | null;
};
export type User = {
email: string?;
emailVerified: Date?;
email?: string | null;
emailVerified?: Date | null;
id: string;
image: string?;
name: string?;
image?: string | null;
name?: string | null;
};
export type GetOffersResponse = {

View File

@@ -9,18 +9,18 @@ export type ResumeComment = Readonly<{
createdAt: Date;
description: string;
id: string;
parentId: string?;
parentId?: string | null;
resumeId: string;
section: ResumesSection;
updatedAt: Date;
user: {
image: string?;
name: string?;
image?: string | null;
name?: string | null;
userId: string;
};
}>;
export type ResumeCommentVote = Readonly<{
numVotes: number;
userVote: ResumesCommentVote?;
userVote?: ResumesCommentVote | null;
}>;

View File

@@ -1,5 +1,5 @@
export type Resume = {
additionalInfo: string?;
additionalInfo?: string | null;
createdAt: Date;
experience: string;
id: string;

View File

@@ -184,7 +184,7 @@ module.exports = {
path: './contents',
routeBasePath: '/',
sidebarPath: require.resolve('./sidebars.js'),
// showLastUpdateAuthor: true,
// ShowLastUpdateAuthor: true,
showLastUpdateTime: true,
},
theme: {

View File

@@ -7,6 +7,7 @@
"start": "docusaurus start",
"dev": "docusaurus start",
"build": "docusaurus build",
"lint": "oxlint docusaurus.config.js sidebars.js src",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
},

View File

@@ -70,27 +70,6 @@ function GreatFrontEnd({ position }) {
);
}
function AlgoMonster({ position }) {
return (
<a
className={clsx(styles.container, styles.backgroundAlgoMonster)}
href="https://shareasale.com/r.cfm?b=1873647&u=3114753&m=114505&urllink=&afftrack="
target="_blank"
rel="noopener"
onClick={() => {
window.gtag('event', `algomonster.${position}.click`);
}}>
<p className={styles.tagline}>
<strong className={styles.title}>
Stop grinding mindlessly. Study with a plan
</strong>
Developed by Google engineers, <u>AlgoMonster</u> is the fastest way to
get a software engineering job. <u>Check it out for free!</u>
</p>
</a>
);
}
function DesignGurusSystemDesign({ position }) {
return (
<a
@@ -177,21 +156,6 @@ export default React.memo(function SidebarAd({ position }) {
);
}
// if (
// path.includes('coding') ||
// path.includes('best-practice-questions') ||
// path.includes('mock-interviews') ||
// path.includes('algorithms')
// ) {
// return rand < 0.3 ? (
// <Interviewingio key={Math.random()} position={position} />
// ) : rand < 0.6 ? (
// <AlgoMonster key={Math.random()} position={position} />
// ) : (
// <DesignGurusCoding key={Math.random()} position={position} />
// );
// }
return rand < 0.5 ? (
<FAANGTechLeads key={Math.random()} position={position} />
) : (

View File

@@ -1,7 +1,6 @@
import React from 'react';
import clsx from 'clsx';
import Layout from '@theme/Layout';
import BrowserOnly from '@docusaurus/BrowserOnly';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
@@ -11,11 +10,10 @@ import successStories from '../data/successStories';
const BLIND_75_URL =
'https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-75-LeetCode-Questions-to-Save-Your-Time-OaM1orEU';
const BLIND_OFFER_NUMBERS_URL =
'https://www.teamblind.com/post/Sharing-my-offer-numbers-from-big-companies-for-your-reference-yNgqUPQR';
const FEATURES = [
{
id: 'zero-to-hero',
title: <>💯 Go From zero to hero</>,
description: (
<>
@@ -26,6 +24,7 @@ const FEATURES = [
link: '/software-engineering-interview-guide/',
},
{
id: 'curated-practice',
title: <>📝 Curated practice questions</>,
description: (
<>
@@ -39,6 +38,7 @@ const FEATURES = [
link: '/coding-interview-study-plan/',
},
{
id: 'best-practices',
title: <>📋 Interview best practices</>,
description: (
<>
@@ -49,6 +49,7 @@ const FEATURES = [
link: '/coding-interview-cheatsheet/',
},
{
id: 'algorithm-tips',
title: <>💁 Practical algorithm tips</>,
description: (
<>
@@ -59,6 +60,7 @@ const FEATURES = [
link: '/algorithms/study-cheatsheet/',
},
{
id: 'behavioral',
title: <>💬 Behavioral questions</>,
description: (
<>
@@ -69,6 +71,7 @@ const FEATURES = [
link: '/behavioral-interview-questions/',
},
{
id: 'tested',
title: <>🧪 Tested and proven</>,
description: (
<>
@@ -220,48 +223,6 @@ function WhatIsThisSection() {
);
}
function RoraSection() {
// Because the SSR and client output can differ and hydration doesn't patch attribute differences,
// we'll render this on the browser only.
return (
<BrowserOnly>
{() => (
<div className={clsx('padding-vert--lg', styles.sectionSponsor)}>
<div className="container">
<div className="row">
<div className="col col--8 col--offset-2">
<div className="margin-vert--lg text--center">
<div>
<h2 className={styles.sectionSponsorTitle}>
<strong>
Get paid more. Receive risk-free salary negotiation
advice from Rora. You pay nothing unless your offer is
increased.
</strong>
</h2>
<div className="margin-vert--lg">
<a
className="button button--secondary button--lg"
href="https://www.teamrora.com/?utm_source=techinterviewhandbook&utm_medium=referral&utm_content=website_homepage"
rel="noopener"
target="_blank"
onClick={() => {
window.gtag('event', 'rora.homepage.click');
}}>
Get risk-free negotiation advice&nbsp;&nbsp;
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)}
</BrowserOnly>
);
}
function HowToUseStep({ index, title, ctaLink, contents }) {
return (
<div className={clsx('card', styles.howToUseStep)}>
@@ -273,8 +234,8 @@ function HowToUseStep({ index, title, ctaLink, contents }) {
</div>
<div className="card__body">
<ul>
{contents.map((content, i) => (
<li key={i}>{content}</li>
{contents.map((content) => (
<li key={content}>{content}</li>
))}
</ul>
</div>
@@ -325,9 +286,9 @@ function HowToUseSection() {
index={1}
title={<>Prepare a FAANG-ready resume</>}
contents={[
<>Create an ATS-proof resume</>,
<>Software engineering specific resume content</>,
<>Optimizing and testing your resume</>,
'Create an ATS-proof resume',
'Software engineering specific resume content',
'Optimizing and testing your resume',
]}
ctaLink="/resume/"
/>
@@ -338,10 +299,10 @@ function HowToUseSection() {
index={2}
title={<>Ace the interviews</>}
contents={[
<>Step-by-step coding interview preparation</>,
<>Algorithms deep dive</>,
<>System design interview preparation</>,
<>Behavioral interview preparation</>,
'Step-by-step coding interview preparation',
'Algorithms deep dive',
'System design interview preparation',
'Behavioral interview preparation',
]}
ctaLink="/coding-interview-prep/"
/>
@@ -352,8 +313,8 @@ function HowToUseSection() {
index={3}
title={<>Negotiate the best offer</>}
contents={[
<>Negotiation strategies for software engineers</>,
<>Guide on how compensation works for software engineers</>,
'Negotiation strategies for software engineers',
'Guide on how compensation works for software engineers',
]}
ctaLink="/understanding-compensation/"
/>
@@ -363,10 +324,7 @@ function HowToUseSection() {
<HowToUseStep
index={4}
title={<>Prepare for the job</>}
contents={[
<>How to choose between companies</>,
<>Guide to engineering levels</>,
]}
contents={['How to choose between companies', 'Guide to engineering levels']}
ctaLink="/choosing-between-companies"
/>
</div>
@@ -394,9 +352,9 @@ function FeaturesSection() {
We have everything you need - all straight to the point
</h3>
<div className={clsx('row', styles.featuresRow)}>
{FEATURES.map(({ title, description, link }, idx) => (
{FEATURES.map(({ id, title, description, link }) => (
<div
key={idx}
key={id}
className={clsx(
'col',
'col--4',
@@ -471,7 +429,7 @@ function GreatFrontEndSection() {
<div className="margin-vert--lg text--center">
<div>
<h2>
<span class="badge badge--secondary">
<span className="badge badge--secondary">
LeetCode for Front End Interviews
</span>
</h2>

View File

@@ -21,6 +21,7 @@
"tsc": "turbo tsc"
},
"devDependencies": {
"oxlint": "^1.56.0",
"oxfmt": "^0.41.0",
"turbo": "2.8.20"
},

View File

@@ -1,148 +0,0 @@
/* eslint-disable sort-keys-fix/sort-keys-fix */
const OFF = 0;
const WARN = 1;
const ERROR = 2;
module.exports = {
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
'simple-import-sort',
'sort-keys-fix',
'typescript-sort-keys',
],
extends: [
'next/core-web-vitals',
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
settings: {
react: {
version: 'detect',
},
},
rules: {
camelcase: [ERROR, { properties: 'never', ignoreDestructuring: true }],
'capitalized-comments': [
ERROR,
'always',
{ ignoreConsecutiveComments: true },
],
'consistent-this': ERROR,
curly: ERROR,
'dot-notation': ERROR,
eqeqeq: [ERROR, 'smart'],
'func-name-matching': ERROR,
'func-names': [ERROR, 'as-needed'],
'func-style': [ERROR, 'declaration', { allowArrowFunctions: true }],
'guard-for-in': ERROR,
'init-declarations': ERROR,
'no-console': [ERROR, { allow: ['warn', 'error', 'info'] }],
'no-else-return': [ERROR, { allowElseIf: false }],
'no-extra-boolean-cast': ERROR,
'no-lonely-if': ERROR,
'no-shadow': OFF,
'no-unused-vars': OFF, // Use @typescript-eslint/no-unused-vars instead.
'object-shorthand': ERROR,
'one-var': [ERROR, 'never'],
'operator-assignment': ERROR,
'prefer-arrow-callback': ERROR,
'prefer-const': ERROR,
'prefer-destructuring': [
ERROR,
{
object: true,
},
],
radix: ERROR,
'spaced-comment': ERROR,
'react/button-has-type': ERROR,
'react/display-name': OFF,
'react/destructuring-assignment': [ERROR, 'always'],
// 'react/hook-use-state': ERROR,
'react/no-array-index-key': ERROR,
'react/no-unescaped-entities': OFF,
'react/void-dom-elements-no-children': ERROR,
'react/jsx-boolean-value': [ERROR, 'always'],
'react/jsx-curly-brace-presence': [
ERROR,
{ props: 'never', children: 'never' },
],
'react/jsx-no-useless-fragment': ERROR,
'react/jsx-sort-props': [
ERROR,
{
callbacksLast: true,
shorthandFirst: true,
reservedFirst: true,
},
],
'@next/next/no-img-element': OFF,
'@next/next/no-html-link-for-pages': OFF,
'@typescript-eslint/array-type': [
ERROR,
{ default: 'generic', readonly: 'generic' },
],
'@typescript-eslint/consistent-generic-constructors': [
ERROR,
'constructor',
],
'@typescript-eslint/consistent-indexed-object-style': [ERROR, 'record'],
'@typescript-eslint/consistent-type-definitions': [ERROR, 'type'],
'@typescript-eslint/consistent-type-imports': ERROR,
'@typescript-eslint/no-duplicate-enum-values': ERROR,
'@typescript-eslint/no-for-in-array': ERROR,
'@typescript-eslint/no-non-null-assertion': OFF,
'@typescript-eslint/no-unused-vars': [ERROR, { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-shadow': ERROR,
'@typescript-eslint/prefer-optional-chain': ERROR,
'@typescript-eslint/require-array-sort-compare': ERROR,
'@typescript-eslint/restrict-plus-operands': ERROR,
'@typescript-eslint/sort-type-union-intersection-members': ERROR,
// Sorting
'typescript-sort-keys/interface': ERROR,
'typescript-sort-keys/string-enum': ERROR,
'sort-keys-fix/sort-keys-fix': ERROR,
'simple-import-sort/exports': WARN,
'simple-import-sort/imports': [
WARN,
{
groups: [
// Ext library & side effect imports.
['^~?\\w', '^\\u0000', '^@'],
// Lib and hooks.
['^~/lib', '^~/hooks'],
// Static data.
['^~/data'],
// Components.
['^~/components'],
// Other imports.
['^~/'],
// Relative paths up until 3 level.
[
'^\\./?$',
'^\\.(?!/?$)',
'^\\.\\./?$',
'^\\.\\.(?!/?$)',
'^\\.\\./\\.\\./?$',
'^\\.\\./\\.\\.(?!/?$)',
'^\\.\\./\\.\\./\\.\\./?$',
'^\\.\\./\\.\\./\\.\\.(?!/?$)',
],
['^~/types'],
// {s}css files
['^.+\\.s?css$'],
// Others that don't fit in.
['^'],
],
},
],
},
};

View File

@@ -1,21 +0,0 @@
{
"name": "eslint-config-tih",
"version": "0.0.0",
"private": true,
"license": "MIT",
"main": "index.js",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint-config-next": "^12.3.1",
"eslint-config-prettier": "^8.5.0",
"eslint-config-turbo": "latest",
"eslint-plugin-react": "7.28.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"eslint-plugin-typescript-sort-keys": "^2.1.0"
},
"publishConfig": {
"access": "public"
}
}

1847
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,17 +16,7 @@
"dependsOn": []
},
"lint": {
"dependsOn": ["^build"],
"env": [
"DATABASE_URL",
"GITHUB_CLIENT_ID",
"GITHUB_CLIENT_SECRET",
"NEXTAUTH_SECRET",
"NEXTAUTH_URL",
"NODE_ENV",
"SUPABASE_ANON_KEY",
"SUPABASE_URL"
]
"outputs": []
},
"tsc": {
"cache": true,