Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
run: npm run markdownlint
- name: format code
run: npm run prettier-check
- name: tsc
run: npm run tsc
coverage:
runs-on: ubuntu-latest
env:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
run: npm run markdownlint
- name: format code
run: npm run prettier-check
- name: tsc
run: npm run tsc
deploy:
runs-on: ubuntu-latest
env:
Expand Down
29 changes: 29 additions & 0 deletions Documentation/Contributors/CodingGuide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ To some extent, this guide can be summarized as _make new code similar to existi
- [Formatting](#formatting)
- [Spelling](#spelling)
- [Linting](#linting)
- [Type Checking](#type-checking)
- [Units](#units)
- [Basic Code Construction](#basic-code-construction)
- [Functions](#functions)
Expand Down Expand Up @@ -172,6 +173,34 @@ try {
/*eslint-enable no-empty*/
```

## Type Checking

We are incrementally adopting [type checking for JavaScript files](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html). As of January 2026, type checks are enabled on a file-by-file basis, with `// @ts-check` annotations at the top of a file used to opt in. As adoption progresses, we expect this pattern will eventually be flipped to an opt-opt annotation instead. To see type system hints and errors in some editors or IDEs, you may need to configure or install TypeScript language server support. When in doubt, VSCode supports TypeScript language services [by default](https://code.visualstudio.com/docs/nodejs/working-with-javascript).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo

Suggested change
We are incrementally adopting [type checking for JavaScript files](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html). As of January 2026, type checks are enabled on a file-by-file basis, with `// @ts-check` annotations at the top of a file used to opt in. As adoption progresses, we expect this pattern will eventually be flipped to an opt-opt annotation instead. To see type system hints and errors in some editors or IDEs, you may need to configure or install TypeScript language server support. When in doubt, VSCode supports TypeScript language services [by default](https://code.visualstudio.com/docs/nodejs/working-with-javascript).
We are incrementally adopting [type checking for JavaScript files](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html). As of January 2026, type checks are enabled on a file-by-file basis, with `// @ts-check` annotations at the top of a file used to opt in. As adoption progresses, we expect this pattern will eventually be flipped to an opt-out annotation instead. To see type system hints and errors in some editors or IDEs, you may need to configure or install TypeScript language server support. When in doubt, VSCode supports TypeScript language services [by default](https://code.visualstudio.com/docs/nodejs/working-with-javascript).


**References:**

For developers already familiar with TypeScript, writing JavaScript with JSDoc type annotations has some differences. For a summary of the annotation syntax, and supported features in JSDoc vs. TSDoc, see:

- [Type Checking JavaScript Files \| TypeScript](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html)
- [JSDoc Reference \| TypeScript](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html)
- [JSDoc Cheatsheet and Type Safety Tricks](https://docs.joshuatz.com/cheatsheets/js/jsdoc/) by Joshua Tzucker
Comment on lines +184 to +186
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to be careful about links like these. TS has sort of expanded or co-opted JSDoc for extra functionality that is not part of JSDoc itself. Our doc generation still uses pure JSDoc so we are beholden to only features it supports. The @import tag is helpful but I feel like that might be the only exception we want to make until our doc generation is updated.

For example in the TS JSDoc Reference it mentions the @staisfies tag. This is not a JSDoc tag and adding it will result in an error ERROR: The @satisfies tag is not a known tag.. I didn't test the others but I'm guessing there are other inconsistencies like this.

Also the very first recommendation in the Cheatsheet shows how to typeguard functions using @returns {value is TYPE}. This is also not recognized by JSDoc and results in an error like:

ERROR: Unable to parse a tag's type expression for source file /home/[user]/Programming/cesium/packages/engine/Source/Core/defined.js in line 28 with tag title "return" and text "{obj is Person}": Invalid type expression "obj is Person": Expected "|" but "i" found.

I bet we could make more edits to our JSDoc config/generation to adapt for more tags but I'm not sure the level of effort there. I mostly bring it up as a word of caution. Maybe all it needs is a warning that our doc gen still needs to work and to be careful. Or a comment that we need to restrict to only official JSDoc tags? Happy to discuss


**Guidelines:**

1. **Incremental adoption with `@ts-check` annotations.** Enabling type checks for existing and new JavaScript files, using `// @ts-check` annotations, is encouraged but not required in the course of ongoing feature development and maintenance. If type-related changes are noisy enough to obscure the functional changes your PR, consider splitting the type fixes into a separate PR.
2. **Common JavaScript/JSDoc obstacles.** JSDoc-based type checks have limited support for some coding patterns used in the CesiumJS codebase, such as classic prototype-based inheritance and `Object.defineProperties()`. In most cases these problems can be solved by refactoring with newer coding patterns, by hand or with tooling like [lebab](https://github.com/lebab/lebab). In other cases the solution may be less obvious, and a temporary skip-check annotation may be used to satisfy the type system. Example with lebab:
```bash
npx lebab packages/engine/Source/Core/Cartesian2.js \
-o packages/engine/Source/Core/Cartesian2.js --transform class
```
3. **Prefer `@ts-expect-error` over `@ts-ignore`.** Sometimes a source file's types are _mostly_ consistent, but a few stubborn errors remain unsolved. These may be blocked by out-of-scope work, or may simply be unjustifiably difficult to solve in JSDoc-based types. Use `@ts-expect-error` annotations to relax the type system for specific lines. Unlike `@ts-ignore`, `@ts-expect-error` will raise errors when the line no longer contains an error, making it easier to clean up annotations later on.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this suggestion for forward compatibility and cleanup. (a quick search led to articles that agree too)

Can we expand this to always include a reason with the disable comment?

It also could be good to look at enforcing this programmatically in eslint. We're already using typescript-eslint for Sandcastle but maybe it's easy to expand to engine files too? It looks like there's a ban-ts-comment rule for this to ban ts-ignore and require-reason for expect-error comments.

4. **Prefer `unknown` or `@ts-expect-error` over `any`.** Casting types to `any` forces them to be accepted everywhere, and effectively disables all type-checking dependent on that type. This tends to propagate throughout a type system, reducing the effectiveness of type checks, and should be avoided when possible. Prefer casting to `unknown`, or using a `@ts-expect-error` annotation. For example, when defining an object with string keys, with values whose types we don't care about, prefer `Record<string, unknown>` to `Record<string, any>`.
5. **Type-only imports.** When JSDoc annotations depend on types not otherwise imported in a source file, it will be necessary to tell TypeScript where to find them. To avoid otherwise-unused imports, use type-only imports in separate one-line JSDoc comments:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The very first google search for "import type in JSDoc" points to an older syntax that still works for the TS side of things

/**
 * @typedef {import('./File1.js').MyObject1} MyObject1
 */

However this is not valid JSDoc and will break things. I think it's worth calling out here to not use this syntax and instead always rely only on the @import tag.

```javascript
/** @import Cartesian3 from './Cartesian3.js'; */
/** @import Cartesian4 from './Cartesian4.js'; */
```

## Units

- Cesium uses SI units:
Expand Down
9 changes: 9 additions & 0 deletions Tools/jsdoc/cesiumTags.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,13 @@ exports.defineTags = function (dictionary) {
canHaveName: true,
mustHaveValue: true,
});

// Allow @import tags for type-only imports. Ignored by JSDoc, but resolved by tsc.
// https://github.com/microsoft/TypeScript/issues/22160#issuecomment-2021459033
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#type-imports-in-jsdoc
dictionary.defineTag("import", {
canHaveType: true,
canHaveName: true,
mustHaveValue: true,
});
};
7 changes: 7 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ export default [
"Avoid Array.push.apply(). Use addAllToArray() for arrays of unknown size, or the spread syntax for arrays that are known to be small",
},
],
// When ES6 class implementations refer to scratch variable instances of
// the same class, ESLint raises a use-before-define error. At runtime
// this is just fine, so configure ESLint to allow it in upper scopes.
"no-use-before-define": [
"error",
{ variables: false, functions: false, classes: false },
],
},
},
{
Expand Down
22 changes: 22 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,28 @@ export async function buildTs() {
await createTypeScriptDefinitions();
}

export async function tsc() {
let workspaces;
if (argv.workspace && !Array.isArray(argv.workspace)) {
workspaces = [argv.workspace];
} else if (argv.workspace) {
workspaces = argv.workspace;
} else {
workspaces = getWorkspaces(true);
}

for (const workspace of workspaces) {
const directory = workspace
.replace(`@${scope}/`, "")
.replace(`packages/`, "");

const tsconfigPath = `packages/${directory}/tsconfig.json`;
if (existsSync(tsconfigPath)) {
execSync(`npx tsc --project ${tsconfigPath}`, { stdio: "inherit" });
}
}
}

const filesToClean = [
"Source/Cesium.js",
"Source/Shaders/**/*.js",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"build-docs": "gulp buildDocs",
"build-docs-watch": "gulp buildDocsWatch",
"eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet",
"tsc": "gulp tsc",
"make-zip": "gulp -f gulpfile.makezip.js makeZip",
"markdownlint": "markdownlint \"**/*.md\"",
"release": "gulp release",
Expand Down
1 change: 0 additions & 1 deletion packages/engine/Source/Scene/Polyline.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ function Polyline(options, polylineCollection) {

this._actualLength = undefined;

// eslint-disable-next-line no-use-before-define
this._propertiesChanged = new Uint32Array(NUMBER_OF_PROPERTIES);
this._polylineCollection = polylineCollection;
this._dirty = false;
Expand Down
4 changes: 4 additions & 0 deletions packages/engine/lint-staged.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default {
// https://github.com/lint-staged/lint-staged#how-can-i-resolve-typescript-tsc-ignoring-tsconfigjson-when-lint-staged-runs-via-husky-hooks
"*.{js,cjs,mjs,ts,cts,mts}": [() => "tsc"],
};
1 change: 1 addition & 0 deletions packages/engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"build": "gulp build --workspace @cesium/engine",
"build-ts": "gulp buildTs --workspace @cesium/engine",
"coverage": "gulp coverage --workspace @cesium/engine",
"tsc": "gulp tsc --workspace @cesium/engine",
"test": "gulp test --workspace @cesium/engine",
"postversion": "gulp postversion --workspace @cesium/engine"
},
Expand Down
21 changes: 21 additions & 0 deletions packages/engine/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"include": ["Source/**/*.js"],
"compilerOptions": {
// Module configuration.
"moduleResolution": "bundler",
"module": "ES2022",
"target": "ES2022",

// I/O.
"noEmit": true,
"allowJs": true,

// Disabled by default. Individual JS files may opt-in to type checking
// by including a `// @ts-check` comment at top of file.
"checkJs": false,

// Checking declarations in dependencies is less important and less
// actionable than checking source JS. Skip until checkJS is on, at least.
"skipLibCheck": true
}
}