Skip to content

Commit

Permalink
opinionated type linting
Browse files Browse the repository at this point in the history
  • Loading branch information
bishopZ committed Sep 17, 2021
1 parent a9244c2 commit 6d45a3b
Show file tree
Hide file tree
Showing 29 changed files with 123 additions and 76 deletions.
47 changes: 38 additions & 9 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ module.exports = {
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": './tsconfig.json',
// "ecmaVersion": 2021, // The version of ECMAScript you are using
// "sourceType": "module", // Enables ECMAScript modules
"ecmaFeatures": {
"jsx": true,
},
"ecmaFeatures": { "jsx": true },
},
"settings": {
"react": {
Expand All @@ -30,19 +29,43 @@ module.exports = {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/consistent-type-assertions": ["error", { "assertionStyle": "as", "objectLiteralTypeAssertions": "allow-as-parameter" }],
"@typescript-eslint/no-implicit-any-catch": ["error", {"allowExplicitAny": false}],
"@typescript-eslint/no-require-imports": ["error"],
"@typescript-eslint/no-unnecessary-boolean-literal-compare": ["error"],
"@typescript-eslint/no-unnecessary-condition": ["error"],
"@typescript-eslint/no-unnecessary-type-constraint": ["error"],
"@typescript-eslint/prefer-for-of": ["error"],
"@typescript-eslint/prefer-includes": ["error"],
"@typescript-eslint/prefer-literal-enum-member": ["error"],
"@typescript-eslint/prefer-nullish-coalescing": ["error"],
"@typescript-eslint/prefer-optional-chain": ["error"],
"@typescript-eslint/prefer-readonly": ["error"],
"@typescript-eslint/prefer-reduce-type-parameter": ["error"],
"@typescript-eslint/prefer-string-starts-ends-with": ["error"],
"@typescript-eslint/promise-function-async": ["error"],
"@typescript-eslint/restrict-plus-operands": ["error"],
"@typescript-eslint/strict-boolean-expressions": ["error"],
"@typescript-eslint/type-annotation-spacing": ["error"],
"@typescript-eslint/unified-signatures": ["error"],
// "@typescript-eslint/prefer-readonly-parameter-types": ["error"],

"array-element-newline": "off",
"arrow-parens": ["error", "as-needed"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"brace-style": "off",
"@typescript-eslint/brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"capitalized-comments": ["error", "never", {
"ignorePattern": ".*"
}],
"curly": ["error", "multi-line"],
"default-param-last": "off",
"@typescript-eslint/default-param-last": "off",
"dot-location": ["error", "property"],
"function-call-argument-newline": ["error", "consistent"],
"function-paren-newline": "off",
"id-length": "off",
"indent": ["error", 2, {
"indent": "off",
"@typescript-eslint/indent": ["error", 2, {
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
Expand Down Expand Up @@ -70,27 +93,33 @@ module.exports = {
"max-nested-callbacks": ["error", 3],
"multiline-comment-style": "off",
"multiline-ternary": ["error", "always-multiline"],
"no-extra-parens": ["error", "functions"],
"no-extra-parens": "off",
"@typescript-eslint/no-extra-parens": ["error", "functions"],
"no-inline-comments": "off",
"no-plusplus": "off",
"no-return-await": "off",
"no-ternary": "off",
"no-trailing-spaces": ["error", { "ignoreComments": true }],
"no-unused-variable": "off",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": ["error"],
"no-warning-comments": "off",
"object-curly-spacing": ["error", "always"],
"object-curly-spacing": "off",
"@typescript-eslint/object-curly-spacing": ["error", "always"],
"object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
"one-var": "off",
"padded-blocks": "off",
"prefer-const": "off",
"quotes": ["error", "single", {
"quotes": "off",
"@typescript-eslint/quotes": ["error", "single", {
"avoidEscape": true,
"allowTemplateLiterals": true }
],
"quote-props": ["error", "as-needed"],
"sort-keys": "off",
"sort-imports": "off",
"space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": "off",
}
};
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ npm run build

+ Added Metadata for SEO, and the favicon fields, and site.manifest

+ Upgraded ESLint to Typescript-ESLint with lots of custom rules for ease of use.
+ Upgraded ESLint to Typescript-ESLint with lots of custom ESLint rules for ease of use, and added opinionated type linting rules and overrides.
+ Added Stylelint for CSS Linting

+ Added an example API
Expand All @@ -86,23 +86,17 @@ npm run build

## TODO

+ Update Node version and review other library versions
+ Host on vercel

+ Lint rules to warn against enums, no-invalid-this, noImplicitThis
+ use an abstract type, T extends HTMLElement = HTMLElement
+ Memoize the Queries, https://stackfull.dev/memoizing-async-functions-in-javascript
+ add useCallback, and useMemo examples
+ Update CSS reset to use water.css?
+ use !! for truthy?
+ generator function example
+ an example of flatMap?
+ requestAnimationFrame example
+ webworker example, https://www.smashingmagazine.com/2021/06/web-workers-2021/

+ mobile-first, responsive CSS
+ better design, mobile-first, responsive CSS
+ Flexbox, https://tobiasahlin.com/blog/common-flexbox-patterns/#masonry-or-mosaic

+ Add a screenshot to the readme

+ Add Suspence
+ Content aware image crop, https://github.com/jwagner/smartcrop.js

Expand All @@ -117,6 +111,17 @@ npm run build
+ https://next-auth.js.org/
+ Node logging, https://blog.appsignal.com/2021/09/01/best-practices-for-logging-in-nodejs.html

+ Host on vercel, when they support Node 16.x
+ use more ??, ${arg2 || null}
+ ask denver devs for a code review
+ review config files, compare to others
+ HTML review, compare to Apple/Airbnb/Twitter

+ use an abstract type, T extends HTMLElement = HTMLElement
+ Update CSS reset to use water.css?
+ an example of flatMap?
+ webworker example, https://www.smashingmagazine.com/2021/06/web-workers-2021/


# Contributors

Expand Down
8 changes: 6 additions & 2 deletions components/atoms/articleAuthor.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import styles from '../../styles/modules/Typography.module.scss';

interface Props { author?: { name?: string; } }
interface Props {
readonly author?: {
readonly name?: string;
}
}

const minLength = 1;

/** generic display of an author's name */
const ArticleAuthor = ({ author }: Props) => (
<>
{ author && author.name && author.name.length >= minLength &&
{ typeof author?.name?.length === 'number' && author.name.length >= minLength &&
<p className={styles.author}>{`by ${author.name}`}</p>
}
</>
Expand Down
2 changes: 1 addition & 1 deletion components/atoms/articleDate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styles from '../../styles/modules/Typography.module.scss';

interface Props { date: Date | string; }
interface Props { readonly date: Date | string; }

/** generic display of a publication date */
const ArticleDate = ({ date }: Props) => (
Expand Down
2 changes: 1 addition & 1 deletion components/atoms/h1.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styles from '../../styles/modules/Typography.module.scss';

/** generic display of the main page title */
const H1 = (props: React.PropsWithChildren<Record<string, unknown>>) => (
const H1 = (props: Readonly<React.PropsWithChildren<Record<string, unknown>>>) => (
<h1 className={styles.h1}>
{props.children}
</h1>
Expand Down
2 changes: 1 addition & 1 deletion components/atoms/h2.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styles from '../../styles/modules/Typography.module.scss';

/** generic display of page subtitles */
const H2 = (props: React.PropsWithChildren<Record<string, unknown>>) => (
const H2 = (props: Readonly<React.PropsWithChildren<Record<string, unknown>>>) => (
<h2 className={styles.h2}>
{props.children}
</h2>
Expand Down
2 changes: 1 addition & 1 deletion components/atoms/imageDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
}

/** generic display of an images */
const ImageDisplay = ({ src, description, height, width, aboveFold }: Props) => (
const ImageDisplay = ({ src, description, height, width, aboveFold }: Readonly<Props>) => (
<Image
alt={description}
src={src as StaticImageData}
Expand Down
2 changes: 1 addition & 1 deletion components/atoms/inputText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class InputText extends Component<Props, JSX.Element> {
tabIndex={index}
value={value}
placeholder={placeholder}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
onChange={(event: Readonly<ChangeEvent<HTMLInputElement>>) => {
onChange(dispatch, event.currentTarget.value);
}}
ref={element => {
Expand Down
8 changes: 4 additions & 4 deletions components/cardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import CardView from './cardView';
import { PostModel } from './database/models';
import styles from '../styles/modules/Cards.module.scss';

interface Props { cards: readonly PostModel[]; }
interface Props { readonly cards: PostModel[]; }

const minLength = 0;
const minLength = 1;

/** a list of cards */
const CardList = ({ cards = [] }: Props) => (
<ul className={styles.cardList}>
{cards.length <= minLength &&
{cards.length < minLength &&
<li className={styles.card}>
<p>No Posts Yet</p>
</li>
}
{cards.length > minLength && cards.map(card =>
{cards.length > minLength && cards.map((card: PostModel) =>
<CardView key={card.id} card={card} />
)}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion components/cardView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import H2 from './atoms/h2';
import ArticleDate from './atoms/articleDate';
import ArticleAuthor from './atoms/articleAuthor';

interface Props { card: PostModel; }
interface Props { readonly card: PostModel }

/** a single card */
const CardView = ({ card }: Props) => (
Expand Down
4 changes: 2 additions & 2 deletions components/database/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface ActionModel {
}

/** for the dispatch context file */
export type Dispatch = (object: ActionModel) => void;
export type Dispatch = (object: Readonly<ActionModel>) => void;

/** single post return object */
export type PostReturn = { props: { post: PostModel } };
Expand Down Expand Up @@ -46,7 +46,7 @@ export type LayoutComponent = {
(props: InferGetStaticPropsType<Promise<PostsReturn>>): JSX.Element;

/** pages implement their own page layout */
getLayout?: (page: Record<string, unknown>) => JSX.Element;
getLayout?: (page: Readonly<Record<string, unknown>>) => JSX.Element;
}

/** model of a single post */
Expand Down
2 changes: 1 addition & 1 deletion components/database/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const getPosts = async (): Promise<PostsQuery> => {
export { getPosts }; // necissary to have both async and arrow function

/** get a post by id */
const getPost = async (params: ParsedUrlQuery): Promise<PostQuery> => {
const getPost = async (params: Readonly<ParsedUrlQuery>): Promise<PostQuery> => {
const defaultIndex = 0;
const id = typeof params.id === 'string'
? params.id
Expand Down
2 changes: 1 addition & 1 deletion components/database/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ let currentState: AppStateModel = { ...initialState };
const newState = () => Object.freeze({ ...currentState });

/** the main application reducer */
export const reducer = (state: AppStateModel, action: ActionModel) => {
export const reducer = (state: Readonly<AppStateModel>, action: Readonly<ActionModel>) => {

// using console here can reveal a lot of about what the application is doing
// console.log(action, state);
Expand Down
5 changes: 3 additions & 2 deletions components/documentDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import ArticleAuthor from './atoms/articleAuthor';
import ImageDisplay from './atoms/imageDisplay';
import exampleImage from '../public/drop.jpg';

interface Props { post: PostModel; }
interface Props { readonly post: PostModel; }

/** a post's detail view */
const DocumentDisplay = ({ post }: Props) => {
const DocumentDisplay = (props: Props) => {

const { post } = props;
const { title, publishDate, author } = post;
return (
<section role="article">
Expand Down
4 changes: 2 additions & 2 deletions components/searchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import InputText from './atoms/inputText';

/** value could be any user input */
interface Props {
value: string;
onChange: (dispatch: Dispatch, value: string) => void;
readonly value: string;
readonly onChange: (dispatch: Dispatch, value: string) => void;
}

/** a controlled input */
Expand Down
2 changes: 1 addition & 1 deletion components/sitewide/footer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styles from '../../styles/modules/Layout.module.scss';

interface Props { message: string; }
interface Props { readonly message: string; }

/** site footer */
const Footer = ({ message }: Props) => (
Expand Down
2 changes: 1 addition & 1 deletion components/sitewide/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import H1 from '../atoms/h1';

interface Props { title: string; }
interface Props { readonly title: string; }

/** site header */
const Header = (props: React.PropsWithChildren<Props>) => (
Expand Down
12 changes: 6 additions & 6 deletions components/sitewide/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import Footer from './footer';
import styles from '../../styles/modules/Layout.module.scss';

interface Props {
children: JSX.Element;
siteInfo: SiteInfoModel;
readonly children: JSX.Element;
readonly siteInfo: SiteInfoModel;
}

/** next layout component */
const Layout = ({ children, siteInfo }: Props) => (
const Layout = (props: Props) => (
<>
<span className={styles.visuallyHidden}>
<a href="#content-begins">Skip to Content</a>
</span>
<Header title={siteInfo.siteName} />
<Header title={props.siteInfo.siteName} />
<main id="content-begins">
{children}
{props.children}
</main>
<Footer message={siteInfo.message} />
<Footer message={props.siteInfo.message} />
</>
);

Expand Down
2 changes: 1 addition & 1 deletion components/sitewide/meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ExternalScripts from './externalScripts';
import PreConnect from './preConnect';
import SeoHeaders from './seoHeaders';

interface Props { siteInfo: SiteInfoModel; }
interface Props { readonly siteInfo: SiteInfoModel; }

/** meta headers for the site */
const MetaHeaders = ({ siteInfo }: Props) => (
Expand Down
2 changes: 1 addition & 1 deletion components/sitewide/seoHeaders.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SiteInfoModel } from '../database/reducer';

interface Props { siteInfo: SiteInfoModel }
interface Props { readonly siteInfo: SiteInfoModel }

/** headers related to search engine ranking */
const SeoHeaders = ({ siteInfo }: Props) => (
Expand Down
1 change: 1 addition & 0 deletions next-sitemap.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

module.exports = {
siteUrl: process.env.SITE_URL ?? 'https://example.com',
changefreq: 'daily',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@
},
"engines": {
"node": "16.9.1",
"npm": "7.23.0"
"npm": "7.24.0"
}
}
2 changes: 1 addition & 1 deletion pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { identity } from 'lodash';
import '../styles/globals.scss';

/** Main application component */
const MyApp = ({ Component, pageProps }: AppProps) => {
const MyApp = ({ Component, pageProps }: Readonly<AppProps>) => {

// Use the layout defined at the page level, if available
type LayoutComponent = typeof Component
Expand Down
File renamed without changes.
Loading

0 comments on commit 6d45a3b

Please sign in to comment.