Skip to content

Commit bcd9644

Browse files
committed
docs: support type info in demo site
1 parent f8b9a47 commit bcd9644

17 files changed

+270
-25
lines changed

docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
preprocess,
77
postprocess,
88
} from "../eslint/scripts/linter.js"
9+
import { loadTsParser } from "../eslint/scripts/ts-parser.js"
10+
import { loadModulesForBrowser } from "../../../../src/shared/svelte-compile-warns/transform/load-module"
911
10-
const linter = createLinter()
12+
const modulesForBrowser = loadModulesForBrowser()
13+
const loadLinter = createLinter()
14+
15+
let tsParser = null
1116
1217
let code = ""
1318
export let rules = {}
@@ -19,6 +24,18 @@
1924
preprocess,
2025
postprocess,
2126
}
27+
$: hasLangTs =
28+
/lang\s*=\s*(?:"ts"|ts|'ts'|"typescript"|typescript|'typescript')/u.test(
29+
code,
30+
)
31+
$: linter = modulesForBrowser.then(
32+
hasLangTs && !tsParser
33+
? async () => {
34+
tsParser = await loadTsParser()
35+
return loadLinter
36+
}
37+
: () => loadLinter,
38+
)
2239
let showDiff = fix
2340
2441
function onLintedResult(evt) {
@@ -48,6 +65,10 @@
4865
parserOptions: {
4966
ecmaVersion: 2020,
5067
sourceType: "module",
68+
parser: {
69+
ts: tsParser,
70+
typescript: tsParser,
71+
},
5172
},
5273
rules,
5374
env: {

docs-svelte-kit/src/lib/components/ESLintPlayground.svelte

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
preprocess,
1010
postprocess,
1111
} from "../eslint/scripts/linter.js"
12+
import { loadTsParser } from "../eslint/scripts/ts-parser.js"
1213
import { loadModulesForBrowser } from "../../../../src/shared/svelte-compile-warns/transform/load-module"
1314
let tsParser = null
1415
const linter = loadModulesForBrowser()
1516
.then(async () => {
16-
tsParser = await import("@typescript-eslint/parser")
17+
tsParser = await loadTsParser()
1718
})
1819
.then(() => {
1920
return createLinter()

docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte

+15-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
lint(linter, code, config, options)
3636
})
3737
38+
let lastResult = {}
39+
3840
async function lint(linter, code, config, options) {
3941
messageMap.clear()
4042
/* eslint-disable no-param-reassign -- ignore */
@@ -69,12 +71,23 @@
6971
fixedMessages: fixResult.messages,
7072
})
7173
72-
leftMarkers = await Promise.all(
74+
lastResult = { messages, fixResult }
75+
76+
const markers = await Promise.all(
7377
messages.map((m) => messageToMarker(m, messageMap)),
7478
)
75-
rightMarkers = await Promise.all(
79+
const fixedMarkers = await Promise.all(
7680
fixResult.messages.map((m) => messageToMarker(m)),
7781
)
82+
if (
83+
lastResult.messages !== messages ||
84+
lastResult.fixResult !== fixResult
85+
) {
86+
// If the result has changed, don't update the markers
87+
return
88+
}
89+
leftMarkers = markers
90+
rightMarkers = fixedMarkers
7891
}
7992
8093
function applyFix() {

docs-svelte-kit/src/lib/eslint/scripts/monaco-loader.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,23 @@ function appendMonacoEditorScript() {
4545
let setupedMonaco = null
4646
let editorLoaded = null
4747

48-
export async function loadMonacoEditor() {
49-
await (setupedMonaco || (setupedMonaco = setupMonaco()))
48+
export function loadMonacoEngine() {
49+
return setupedMonaco || (setupedMonaco = setupMonaco())
50+
}
51+
export function loadMonacoEditor() {
5052
return (
5153
editorLoaded ||
52-
(editorLoaded = new Promise((resolve) => {
53-
if (typeof window !== "undefined") {
54-
// eslint-disable-next-line node/no-missing-require -- ignore
55-
window.require(["vs/editor/editor.main"], (r) => {
56-
resolve(r)
57-
})
58-
}
59-
}))
54+
(editorLoaded = loadModuleFromMonaco("vs/editor/editor.main"))
6055
)
6156
}
57+
58+
export async function loadModuleFromMonaco(moduleName) {
59+
await loadMonacoEngine()
60+
return new Promise((resolve) => {
61+
if (typeof window !== "undefined") {
62+
window.require([moduleName], (r) => {
63+
resolve(r)
64+
})
65+
}
66+
})
67+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type typescript from "typescript"
2+
import type tsvfs from "@typescript/vfs"
3+
type TS = typeof typescript
4+
type TSVFS = typeof tsvfs
5+
6+
/** Create Program */
7+
export function createProgram(
8+
{
9+
ts,
10+
compilerOptions,
11+
compilerHost,
12+
}: {
13+
ts: TS
14+
compilerOptions: typescript.CompilerOptions
15+
compilerHost: typescript.CompilerHost
16+
},
17+
options: { filePath: string },
18+
): typescript.Program {
19+
try {
20+
const program = ts.createProgram({
21+
rootNames: [options.filePath],
22+
options: compilerOptions,
23+
host: compilerHost,
24+
})
25+
return program
26+
} catch (e) {
27+
// eslint-disable-next-line no-console -- Demo debug
28+
console.error(e)
29+
throw e
30+
}
31+
}
32+
33+
export function createCompilerOptions(ts: TS): typescript.CompilerOptions {
34+
const compilerOptions: typescript.CompilerOptions = {
35+
target: ts.ScriptTarget.ESNext,
36+
module: ts.ModuleKind.ESNext,
37+
jsx: ts.JsxEmit.Preserve,
38+
strict: true,
39+
}
40+
compilerOptions.lib = [ts.getDefaultLibFileName(compilerOptions)]
41+
return compilerOptions
42+
}
43+
44+
export async function createVirtualCompilerHost(
45+
{
46+
ts,
47+
tsvfs,
48+
compilerOptions,
49+
}: {
50+
ts: TS
51+
tsvfs: TSVFS
52+
compilerOptions: typescript.CompilerOptions
53+
},
54+
{ filePath: targetFilePath }: { filePath: string },
55+
): Promise<{
56+
compilerHost: typescript.CompilerHost
57+
updateFile: (sourceFile: typescript.SourceFile) => boolean
58+
fsMap: Map<string, string>
59+
}> {
60+
const fsMap = await tsvfs.createDefaultMapFromCDN(
61+
{
62+
lib: Array.from(
63+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- use internal
64+
(ts as any).libMap.keys(),
65+
),
66+
},
67+
ts.version,
68+
true,
69+
ts,
70+
)
71+
const system = tsvfs.createSystem(fsMap)
72+
const host = tsvfs.createVirtualCompilerHost(system, compilerOptions, ts)
73+
// eslint-disable-next-line @typescript-eslint/unbound-method -- backup original
74+
const original = { getSourceFile: host.compilerHost.getSourceFile }
75+
host.compilerHost.getSourceFile = function (
76+
fileName,
77+
languageVersionOrOptions,
78+
...args
79+
) {
80+
if (targetFilePath === fileName) {
81+
// Exclude the target file from caching as it will be modified.
82+
const file = this.readFile(fileName) ?? ""
83+
return ts.createSourceFile(fileName, file, languageVersionOrOptions, true)
84+
}
85+
if (this.fileExists(fileName)) {
86+
return original.getSourceFile.apply(this, [
87+
fileName,
88+
languageVersionOrOptions,
89+
...args,
90+
])
91+
}
92+
// Avoid error
93+
// eslint-disable-next-line no-console -- Demo debug
94+
console.log(`Not exists: ${fileName}`)
95+
return undefined
96+
}
97+
return {
98+
...host,
99+
fsMap,
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { loadMonacoEngine } from "./monaco-loader"
2+
import {
3+
createProgram,
4+
createCompilerOptions,
5+
createVirtualCompilerHost,
6+
} from "./ts-create-program.mts"
7+
8+
let tsParserCache = null
9+
export function loadTsParser() {
10+
return (tsParserCache ??= loadTsParserImpl())
11+
}
12+
13+
async function loadTsParserImpl() {
14+
await loadMonacoEngine()
15+
const [ts, tsvfs, tsParser] = await Promise.all([
16+
import("typescript"),
17+
import("@typescript/vfs"),
18+
import("@typescript-eslint/parser"),
19+
])
20+
if (typeof window === "undefined") {
21+
return tsParser
22+
}
23+
window.define("typescript", ts)
24+
25+
const compilerOptions = createCompilerOptions(ts)
26+
const filePath = "/demo.ts"
27+
const host = await createVirtualCompilerHost(
28+
{ ts, tsvfs, compilerOptions },
29+
{ filePath },
30+
)
31+
return {
32+
parseForESLint(code, options) {
33+
host.fsMap.set(filePath, code)
34+
// Requires its own Program instance to provide full type information.
35+
const program = createProgram(
36+
{ ts, compilerHost: host.compilerHost, compilerOptions },
37+
{ filePath },
38+
)
39+
40+
try {
41+
const result = tsParser.parseForESLint(code, {
42+
...options,
43+
filePath: filePath.replace(/^\//u, ""),
44+
programs: [program],
45+
})
46+
return result
47+
} catch (e) {
48+
// eslint-disable-next-line no-console -- Demo debug
49+
console.error(e)
50+
throw e
51+
}
52+
},
53+
}
54+
}

docs/rules/@typescript-eslint/no-unnecessary-condition.md

+22-11
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ description: "disallow conditionals where the type is always truthy or always fa
1414

1515
## :book: Rule Details
1616

17-
This rule reports ???.
17+
This rule extends the base `@typescript-eslint`'s [@typescript-eslint/no-unnecessary-condition] rule.
18+
The [@typescript-eslint/no-unnecessary-condition] rule does not understand reactive or rerendering of Svelte components and has false positives when used with Svelte components. This rule understands reactive and rerendering of Svelte components.
1819

1920
<ESLintCodeBlock fix>
2021

2122
<!--eslint-skip-->
2223

2324
```svelte
24-
<script>
25+
<script lang="ts">
2526
/* eslint svelte/@typescript-eslint/no-unnecessary-condition: "error" */
27+
export let foo: number | null = null
28+
/* ✗ BAD */
29+
let b = foo || 42
30+
/* ✓ GOOD */
31+
$: a = foo || 42
2632
</script>
2733
2834
<!-- ✓ GOOD -->
29-
30-
<!-- ✗ BAD -->
31-
35+
{foo || 42}
3236
```
3337

3438
</ESLintCodeBlock>
@@ -37,17 +41,24 @@ This rule reports ???.
3741

3842
```json
3943
{
40-
"svelte/@typescript-eslint/no-unnecessary-condition": ["error", {
41-
42-
}]
44+
"@typescript-eslint/no-unnecessary-condition": "off",
45+
"svelte/@typescript-eslint/no-unnecessary-condition": [
46+
"error",
47+
{
48+
"allowConstantLoopConditions": false,
49+
"allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": false
50+
}
51+
]
4352
}
4453
```
4554

46-
-
55+
Same as [@typescript-eslint/no-unnecessary-condition] rule option. See [here](https://typescript-eslint.io/rules/no-unnecessary-condition/#options) for details.
56+
57+
## :couple: Related rules
4758

48-
## :books: Further Reading
59+
- [@typescript-eslint/no-unnecessary-condition]
4960

50-
-
61+
[@typescript-eslint/no-unnecessary-condition]: https://typescript-eslint.io/rules/no-unnecessary-condition/
5162

5263
## :mag: Implementation
5364

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"@typescript-eslint/eslint-plugin": "^5.4.0",
113113
"@typescript-eslint/parser": "^5.4.1-0",
114114
"@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@4",
115+
"@typescript/vfs": "^1.4.0",
115116
"assert": "^2.0.0",
116117
"commitlint": "^17.0.3",
117118
"env-cmd": "^10.1.0",

tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-errors.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
line: 4
33
column: 13
44
suggestions: null
5+
- message: Unnecessary conditional, both sides of the expression are literal values.
6+
line: 8
7+
column: 9
8+
suggestions: null

tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-input.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@
33
44
const a = foo == null
55
$: b = foo == null
6+
$: {
7+
let bar: null | boolean = null
8+
c = bar == null
9+
}
610
</script>

tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-output.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@
33
44
const a = foo == null
55
$: b = foo == null
6+
$: {
7+
let bar: null | boolean = null
8+
c = bar == null
9+
}
610
</script>

tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-errors.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,8 @@
33
line: 5
44
column: 13
55
suggestions: null
6+
- message: Unnecessary conditional, left-hand side of `??` operator is always
7+
`null` or `undefined`.
8+
line: 9
9+
column: 9
10+
suggestions: null

tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-input.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
55
const a = foo ?? bar
66
$: b = foo ?? bar
7+
$: {
8+
let baz: null | boolean = null
9+
c = baz ?? bar
10+
}
711
</script>

0 commit comments

Comments
 (0)