Linting is not the same as formatting: ^3
- Linting is about sticking to coding best practices, i.e. if a language allows multiple constructs do achieve a functionality, the linter enforces using the generally agreed upon (or identifies constructs that are generally considered bugs e.g. unused variables).
- Formatting is about where linebreaks are, how many spaces / tabs are used, where brackets are located. It does not care about coding best practices or finding bugs.
Linters find issues, formatters change code layout ^4. Usually you want both, a linter (e.g. eslint) and a formatter (prettier).
More details on how this interacts with npm later, but essentially:
npm init @eslint/config@latest(opens a dialogue, select your options withspace, then enter)- Creates a new config file:
eslint.config.mts(the actual linter configuration) - Modifies
package.json, by adding dependencies for the linter to thedevDependenciessection. - One extra dev dependency is required, because we're working with typescript:
- Add to
devDependencies:"jiti": "^2.6.1" - Then reinstall dev dependencies, with:
npm install --only=dev
- Add to
- Creates a new config file:
- Now we can actually run the linter:
npx eslint src
Note:
devDependenciesare dependencies needed forbuilding, i.e. not needed during program execution.
EsLint does not require comments, but extending eslint with an extra package just for comments is possible:
- Install eslint extension:
npm install --save-dev eslint-plugin-jsdoc - Is identical to manually adding to
package.json/dev-dependencies:"eslint-plugin-jsdoc": "^61.5.0", followed bynpm install --only=dev\
The comment extension still needs configuration.
Edit eslint.config.mts, add two things:
- An import:
// Additional import required for linting (requiring) comments
import jsdoc from 'eslint-plugin-jsdoc';- A configuration, specifically for ts files (an additional entry to the existing
defineConfigarray):
// Additionally, require typescript files to be commented (requires jsdoc plugin)
{
files: ["**/*.{ts,mts,cts}"],
plugins: { jsdoc },
rules: {
"jsdoc/require-jsdoc": [
"error",
{
// Require comments for classes, methods, and constructors
contexts: [
"ArrowFunctionExpression",
"ClassDeclaration",
"FunctionDeclaration",
'MethodDefinition',
],
},
],
},
}Finally, test it with npm run lint -> shows errors for missing class documentation.
The linking between individual typescript (import / export syntax), works differently depending on the interpreter executing the produced JS files.
- When interpreted by Node, the typescript files can be simply compiled as they are. Node will understands how individual files (modules) refer to one anoterh.
- When interpreted by a browser, a little extra magic is needed, because the browser does not understand the typescript specific import syntax. There's a hack: adding the
.jsfile extension for every import, but that is contrived. Better is to produce something on compilation that the browser understands. This is called "bundling", i.e. a command is used to fuse all produces JS files to a singlebundle.jsfile.
npm only calls the tsc command. It is prefectly fine to manually compile using the tsc command:
- Install with
brew install tsc - Compile files with
tsc file.ts(produces.jsfile.)
Or, to have a cleaner process:
- Build, i.e. compile all typescript files:
tsc src/*.ts - Run (using node interpreter):
node src/Launcher.js - Clean up:
rm src/*js
- Create a default package.json file:
npm init -y - Update at least the
authorfield. npmscripts can then be defined in thescriptsfield, e.g.:
"scripts": {
"lint": "echo linting...",
"exec": "tsc src/*.ts; node ./src/SpirolateralCurveGenerator.js; rm src/*.js"
}- To run any defined script, use:
npm run exec(or replaceexecby any script name).
The bundler is esbuild. It is called like this:
npx esbuild src/webui.ts --bundle --format=esm --platform=browser --outfile=docs/bundle.js
There are a few caveats:
- The bundler optimizes the code, this also means it will strip a function if never called.
- Check the size of the generated
bundle.js, must at least have 10-100kb.
- Check the size of the generated
- There should be a separation between sources and generated js files. - In this repo, the
srclies next to thedocsrepo (docs is deployed by github pages) - Configuration of source and target takes place via:tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "ES2015",
"rootDir": "src",
"outDir": "docs",
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*.ts"]
}To install a local dependency, e.g. a linter: npm init @eslint/config@latest
Runs packages, e.g. the linter - but does not need the package to be defined in
package.json.
So in essence this is an alternative to:
- First defining a dependency in
scriptsection ofpackage.json - Then calling that script with
npm run name-of-script
Tip: Define script, that invokes npx, e.g.:
-
Create a new directory
tests -
Add individual tests using the naming convention
foo.test.ts -
Install jest:
- Jest itself:
npm install --save-dev jest ts-jest @types/jest - Jest typescript support:
npm install ts-jest --save-dev - Jest type support:
npm install @types/jest --save-dev
- Jest itself:
-
Finally, configure jest via
package.jsonto handle the sources as typescript:
"jest": {
"preset": "ts-jest",
"testEnvironment": "node"
}Import the function you want to test, then define what is expected:
import { Turtle } from "../src/Turtle";
describe("testing if advance correctly displaces north facing turtle", () => {
test("turtle at expected x/y position", () => {
const turtle = new Turtle(0, 0, 0);
turtle.advance(1);
expect(turtle.getPositionX()).toBe(0);
expect(turtle.getPositionY()).toBe(-1);
});
});This one is a bit tricky, because there is an overlap between EsLint (which mainly lints and does some minimal formatting), and Prettier (which is a fully fletched formatter).
In short: A mismatch can be frustrating, because files formatted with prettier are rejected at the linter phase. So we need to override EsLint's minimal formatting with Prettiers.
- Format code:
⌥ + ⇧ + f
Note: This must be configured to invoke Prettier in
Code -> Settings -> Settings -> Text Editor -> Default Formatter.
Prettier's default formatting rules are good, but some adjustments make sense, e.g. configuring a max line length (which unfortunately is never applied on comments).
-
Override EsLint formatting, so the linter does not get into our way when it comes to formatting.
-
Install the bridge between prettier en eslint:
npm install --save-dev eslint-config-prettier -
Override EsLint's formatting checks to whatever prettier produces in
eslint.config.mtsimport prettier from "eslint-config-prettier";- Add to
default(all languages) rule:
// Override eslint formatting rules, with prettier (an actual formatter) rules, apply to ALL files. prettier, { rules: { "max-len": [ "warn", { code: 120, ignoreUrls: true, ignoreStrings: false, ignoreTemplateLiterals: false, }, ], }, },
-
-
Configure Prettier to hard wrap on 120 characters, in
.prettierrc(will not auto wrap comments)
{
"printWidth": 120,
"tabWidth": 4,
"useTabs": false
}About comments: Since eslint now uses prettiers formatting requirements, comments with more than 120 characters still raise a warning in the IDE. They need manual wrapping though. Good trick is to add a visual ruler to
.vscode/settings.json, with"editor.rulers": [120].
Browsers do not allow dynamic manipulation of local svg files, it is essential to access the webapp through a file server, rather than via file system.
The easiest way the python server.
cd SpirolateralCurves/docs
python3 -m http.server &
open http://127.0.0.1:8000