From 1ea2f12148009280e2686449d9c1cdbb0e23beac Mon Sep 17 00:00:00 2001 From: Orta Date: Mon, 24 Aug 2020 11:36:03 -0400 Subject: [PATCH 1/3] Prepare Twoslash for 1.0 --- .../gatsby-remark-shiki-twoslash/package.json | 4 +- packages/shiki-twoslash/package.json | 4 +- packages/ts-twoslasher/CHANGELOG.md | 5 + packages/ts-twoslasher/package.json | 4 +- packages/ts-twoslasher/src/index.ts | 45 +++++--- packages/ts-twoslasher/test/fixtures.test.ts | 64 +++++------ .../test/fixtures/importsModules.ts | 14 +++ .../test/fixtures/tests/handlesJSON.ts | 6 ++ .../test/results/importsModules.json | 100 ++++++++++++++++++ .../test/results/tests/handlesJSON.json | 19 ++++ packages/typescript-vfs/CHANGELOG.md | 7 ++ packages/typescript-vfs/package.json | 2 +- packages/typescript-vfs/src/index.ts | 60 ++++++----- packages/typescript-vfs/test/fsbacked.test.ts | 59 +++++++++++ packages/typescript-vfs/test/index.test.ts | 18 ---- packages/typescriptlang-org/package.json | 2 +- 16 files changed, 313 insertions(+), 100 deletions(-) create mode 100644 packages/ts-twoslasher/test/fixtures/importsModules.ts create mode 100644 packages/ts-twoslasher/test/fixtures/tests/handlesJSON.ts create mode 100644 packages/ts-twoslasher/test/results/importsModules.json create mode 100644 packages/ts-twoslasher/test/results/tests/handlesJSON.json create mode 100644 packages/typescript-vfs/CHANGELOG.md create mode 100644 packages/typescript-vfs/test/fsbacked.test.ts diff --git a/packages/gatsby-remark-shiki-twoslash/package.json b/packages/gatsby-remark-shiki-twoslash/package.json index 61edab96e4d5..600502124eb2 100644 --- a/packages/gatsby-remark-shiki-twoslash/package.json +++ b/packages/gatsby-remark-shiki-twoslash/package.json @@ -19,8 +19,8 @@ "lint": "tsdx lint" }, "dependencies": { - "@typescript/twoslash": "0.6.2", - "@typescript/vfs": "1.1.2", + "@typescript/twoslash": "1.0.0", + "@typescript/vfs": "1.2.0", "shiki": "^0.1.6", "shiki-languages": "^0.1.6", "shiki-twoslash": "0.7.0", diff --git a/packages/shiki-twoslash/package.json b/packages/shiki-twoslash/package.json index 6775ad367d3c..b92977d4bcbd 100644 --- a/packages/shiki-twoslash/package.json +++ b/packages/shiki-twoslash/package.json @@ -20,8 +20,8 @@ "lint": "tsdx lint" }, "dependencies": { - "@typescript/twoslash": "0.6.2", - "@typescript/vfs": "1.1.2", + "@typescript/twoslash": "1.0.0", + "@typescript/vfs": "1.2.0", "shiki": "^0.1.6", "shiki-languages": "^0.1.6", "typescript": "*" diff --git a/packages/ts-twoslasher/CHANGELOG.md b/packages/ts-twoslasher/CHANGELOG.md index 42f50bf8ed04..e0d3290e6c6d 100644 --- a/packages/ts-twoslasher/CHANGELOG.md +++ b/packages/ts-twoslasher/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.0.0 + +- Supports falling back to _your project's_ node modules for resolving types and imports. This drastically simplifies setting up a code sample which relies on types not shipped with TypeScript. +- Support for adding vfs JSON files in a code sample + ## 0.5.0 - Support TS 4.0 diff --git a/packages/ts-twoslasher/package.json b/packages/ts-twoslasher/package.json index 2f66910fdd56..4d548ab3630d 100755 --- a/packages/ts-twoslasher/package.json +++ b/packages/ts-twoslasher/package.json @@ -1,6 +1,6 @@ { "name": "@typescript/twoslash", - "version": "0.6.2", + "version": "1.0.0", "license": "MIT", "author": "TypeScript team", "main": "dist/index.js", @@ -39,7 +39,7 @@ "lz-string": false }, "dependencies": { - "@typescript/vfs": "1.1.2", + "@typescript/vfs": "1.2.0", "debug": "^4.1.1", "lz-string": "^1.4.4" } diff --git a/packages/ts-twoslasher/src/index.ts b/packages/ts-twoslasher/src/index.ts index 926116e9aae7..ad849cef338f 100755 --- a/packages/ts-twoslasher/src/index.ts +++ b/packages/ts-twoslasher/src/index.ts @@ -1,7 +1,7 @@ let hasLocalStorage = false try { hasLocalStorage = typeof localStorage !== `undefined` -} catch (error) { } +} catch (error) {} const hasProcess = typeof process !== `undefined` const shouldDebug = (hasLocalStorage && localStorage.getItem("DEBUG")) || (hasProcess && process.env.DEBUG) @@ -21,7 +21,7 @@ import { } from "./utils" import { validateInput, validateCodeForErrors } from "./validation" -import { createSystem, createVirtualTypeScriptEnvironment, createDefaultMapFromNodeModules } from "@typescript/vfs" +import { createSystem, createVirtualTypeScriptEnvironment, createFSBackedSystem } from "@typescript/vfs" const log = shouldDebug ? console.log : (_message?: any, ..._optionalParams: any[]) => "" @@ -171,7 +171,7 @@ const valuedConfigRegexp = /^\/\/\s?@(\w+):\s?(.+)$/ function filterCompilerOptions(codeLines: string[], defaultCompilerOptions: CompilerOptions, ts: TS) { const options = { ...defaultCompilerOptions } - for (let i = 0; i < codeLines.length;) { + for (let i = 0; i < codeLines.length; ) { let match if ((match = booleanConfigRegexp.exec(codeLines[i]))) { options[match[1]] = true @@ -383,8 +383,12 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt const handbookOptions = { ...filterHandbookOptions(codeLines), ...options.defaultOptions } const compilerOptions = filterCompilerOptions(codeLines, defaultCompilerOptions, ts) - const vfs = options.fsMap ?? createLocallyPoweredVFS(compilerOptions, ts) - const system = createSystem(vfs) + // In a browser we want to DI everything, in node we can use local infra + const useFS = !!options.fsMap + const vfs = useFS && options.fsMap ? options.fsMap : new Map() + const system = useFS ? createSystem(vfs) : createFSBackedSystem(vfs, process.cwd(), ts) + const fsRoot = useFS ? "/" : process.cwd() + "/" + const env = createVirtualTypeScriptEnvironment(system, [], ts, compilerOptions, options.customTransformers) const ls = env.languageService @@ -394,13 +398,20 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt let queries = [] as TwoSlashReturn["queries"] let highlights = [] as TwoSlashReturn["highlights"] - const nameContent = splitTwoslashCodeInfoFiles(code, defaultFileName) + const nameContent = splitTwoslashCodeInfoFiles(code, defaultFileName, fsRoot) + const sourceFiles = ["js", "jsx", "ts", "tsx"] /** All of the referenced files in the markup */ const filenames = nameContent.map(nc => nc[0]) for (const file of nameContent) { const [filename, codeLines] = file + const filetype = filename.split(".").pop() || "" + + // Only run the LSP-y things on source files + if (!sourceFiles.includes(filetype)) { + continue + } // Create the file in the vfs const newFileCode = codeLines.join("\n") @@ -492,6 +503,13 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt // const declaredFiles = Object.keys(fileMap) filenames.forEach(file => { + const filetype = file.split(".").pop() || "" + + // Only run the LSP-y things on source files + if (!sourceFiles.includes(filetype)) { + return + } + if (!handbookOptions.noErrors) { errs.push(...ls.getSemanticDiagnostics(file)) errs.push(...ls.getSyntacticDiagnostics(file)) @@ -598,18 +616,18 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt if (handbookOptions.showEmit) { // Get the file which created the file we want to show: const emitFilename = handbookOptions.showEmittedFile || defaultFileName - const emitSourceFilename = emitFilename.replace(".js", "").replace(".d.ts", "").replace(".map", "") + const emitSourceFilename = fsRoot + emitFilename.replace(".js", "").replace(".d.ts", "").replace(".map", "") const emitSource = filenames.find(f => f === emitSourceFilename + ".ts" || f === emitSourceFilename + ".tsx") if (!emitSource) { const allFiles = filenames.join(", ") throw new Error( - `Cannot find the corresponding source file for ${emitFilename} ${handbookOptions.showEmittedFile} - in ${allFiles}` + `Cannot find the corresponding source file for ${emitFilename} (looking for: ${emitSourceFilename} in the vfs) - in ${allFiles}` ) } const output = ls.getEmitOutput(emitSource) - const file = output.outputFiles.find(o => o.name === handbookOptions.showEmittedFile) + const file = output.outputFiles.find(o => o.name === fsRoot + handbookOptions.showEmittedFile) if (!file) { const allFiles = output.outputFiles.map(o => o.name).join(", ") throw new Error(`Cannot find the file ${handbookOptions.showEmittedFile} - in ${allFiles}`) @@ -676,10 +694,7 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt } } -const createLocallyPoweredVFS = (compilerOptions: CompilerOptions, ts?: typeof import("typescript")) => - createDefaultMapFromNodeModules(compilerOptions, ts) - -const splitTwoslashCodeInfoFiles = (code: string, defaultFileName: string) => { +const splitTwoslashCodeInfoFiles = (code: string, defaultFileName: string, root: string) => { const lines = code.split(/\r\n?|\n/g) let nameForFile = code.includes(`@filename: ${defaultFileName}`) ? "global.ts" : defaultFileName @@ -688,14 +703,14 @@ const splitTwoslashCodeInfoFiles = (code: string, defaultFileName: string) => { for (const line of lines) { if (line.includes("// @filename: ")) { - fileMap.push([nameForFile, currentFileContent]) + fileMap.push([root + nameForFile, currentFileContent]) nameForFile = line.split("// @filename: ")[1].trim() currentFileContent = [] } else { currentFileContent.push(line) } } - fileMap.push([nameForFile, currentFileContent]) + fileMap.push([root + nameForFile, currentFileContent]) // Basically, strip these: // ["index.ts", []] diff --git a/packages/ts-twoslasher/test/fixtures.test.ts b/packages/ts-twoslasher/test/fixtures.test.ts index c2e7638ce1f7..651c77af5349 100644 --- a/packages/ts-twoslasher/test/fixtures.test.ts +++ b/packages/ts-twoslasher/test/fixtures.test.ts @@ -1,34 +1,34 @@ -import { readdirSync, readFileSync, lstatSync } from 'fs' -import { join, extname, parse } from 'path' -import { toMatchFile } from 'jest-file-snapshot' -import { twoslasher } from '../src/index' -import { format } from 'prettier' +import { readdirSync, readFileSync, lstatSync } from "fs" +import { join, extname, parse } from "path" +import { toMatchFile } from "jest-file-snapshot" +import { twoslasher } from "../src/index" +import { format } from "prettier" expect.extend({ toMatchFile }) // To add a test, create a file in the fixtures folder and it will will run through // as though it was the codeblock. -describe('with fixtures', () => { +describe("with fixtures", () => { // Add all codefixes - const fixturesFolder = join(__dirname, 'fixtures') - const resultsFolder = join(__dirname, 'results') + const fixturesFolder = join(__dirname, "fixtures") + const resultsFolder = join(__dirname, "results") - readdirSync(join(fixturesFolder, 'tests')).forEach(fixtureName => { - const fixture = join(fixturesFolder, 'tests', fixtureName) + readdirSync(join(fixturesFolder, "tests")).forEach(fixtureName => { + const fixture = join(fixturesFolder, "tests", fixtureName) if (lstatSync(fixture).isDirectory()) { return } - // if(!fixtureName.includes("compiler_fl")) return - it('Hidden Fixtures: ' + fixtureName, () => { - const resultName = parse(fixtureName).name + '.json' - const result = join(resultsFolder, 'tests', resultName) + // if(!fixtureName.includes("imports")) return + it("Hidden Fixtures: " + fixtureName, () => { + const resultName = parse(fixtureName).name + ".json" + const result = join(resultsFolder, "tests", resultName) - const file = readFileSync(fixture, 'utf8') + const file = readFileSync(fixture, "utf8") const fourslashed = twoslasher(file, extname(fixtureName).substr(1)) - const jsonString = format(JSON.stringify(fourslashed), { parser: 'json' }) + const jsonString = format(JSON.stringify(fourslashed), { parser: "json" }) expect(jsonString).toMatchFile(result) }) }) @@ -40,38 +40,38 @@ describe('with fixtures', () => { } // if(!fixtureName.includes("compiler_fl")) return - it('README Fixtures: ' + fixtureName, () => { - const resultName = parse(fixtureName).name + '.json' + it("README Fixtures: " + fixtureName, () => { + const resultName = parse(fixtureName).name + ".json" const result = join(resultsFolder, resultName) - const file = readFileSync(fixture, 'utf8') + const file = readFileSync(fixture, "utf8") const fourslashed = twoslasher(file, extname(fixtureName).substr(1)) - const jsonString = format(JSON.stringify(fourslashed), { parser: 'json' }) + const jsonString = format(JSON.stringify(fourslashed), { parser: "json" }) expect(jsonString).toMatchFile(result) }) }) - readdirSync(join(fixturesFolder, 'tests')).forEach(fixtureName => { - const fixture = join(fixturesFolder, 'tests', fixtureName) + readdirSync(join(fixturesFolder, "tests")).forEach(fixtureName => { + const fixture = join(fixturesFolder, "tests", fixtureName) if (lstatSync(fixture).isDirectory()) { return } // if(!fixtureName.includes("compiler_fl")) return - it('Hidden Fixtures: ' + fixtureName, () => { - const resultName = parse(fixtureName).name + '.json' - const result = join(resultsFolder, 'tests', resultName) + it("Hidden Fixtures: " + fixtureName, () => { + const resultName = parse(fixtureName).name + ".json" + const result = join(resultsFolder, "tests", resultName) - const file = readFileSync(fixture, 'utf8') + const file = readFileSync(fixture, "utf8") const fourslashed = twoslasher(file, extname(fixtureName).substr(1)) - const jsonString = format(JSON.stringify(fourslashed), { parser: 'json' }) + const jsonString = format(JSON.stringify(fourslashed), { parser: "json" }) expect(jsonString).toMatchFile(result) }) }) - const throwingFixturesFolder = join(__dirname, 'fixtures', 'throws') + const throwingFixturesFolder = join(__dirname, "fixtures", "throws") readdirSync(throwingFixturesFolder).forEach(fixtureName => { const fixture = join(throwingFixturesFolder, fixtureName) @@ -79,11 +79,11 @@ describe('with fixtures', () => { return } - it('Throwing Fixture: ' + fixtureName, () => { - const resultName = parse(fixtureName).name + '.json' + it("Throwing Fixture: " + fixtureName, () => { + const resultName = parse(fixtureName).name + ".json" const result = join(resultsFolder, resultName) - const file = readFileSync(fixture, 'utf8') + const file = readFileSync(fixture, "utf8") let thrown = false try { @@ -93,7 +93,7 @@ describe('with fixtures', () => { expect(err.message).toMatchFile(result) } - if (!thrown) throw new Error('Did not throw') + if (!thrown) throw new Error("Did not throw") }) }) }) diff --git a/packages/ts-twoslasher/test/fixtures/importsModules.ts b/packages/ts-twoslasher/test/fixtures/importsModules.ts new file mode 100644 index 000000000000..d4dcd618fed4 --- /dev/null +++ b/packages/ts-twoslasher/test/fixtures/importsModules.ts @@ -0,0 +1,14 @@ +// @filename: Component.tsx +import React from "react" + +export function Hello() { + return ( +
+

Hello World

+
+ ) +} + +// @filename: index.ts +import { Hello } from "./Component" +console.log(Hello) diff --git a/packages/ts-twoslasher/test/fixtures/tests/handlesJSON.ts b/packages/ts-twoslasher/test/fixtures/tests/handlesJSON.ts new file mode 100644 index 000000000000..a7df1b6dd371 --- /dev/null +++ b/packages/ts-twoslasher/test/fixtures/tests/handlesJSON.ts @@ -0,0 +1,6 @@ +// @moduleResolution: node +// @filename: package.json +{ "name": "thing" } +// @filename: index.ts +// ---cut--- +const i = 123 diff --git a/packages/ts-twoslasher/test/results/importsModules.json b/packages/ts-twoslasher/test/results/importsModules.json new file mode 100644 index 000000000000..723da382baa8 --- /dev/null +++ b/packages/ts-twoslasher/test/results/importsModules.json @@ -0,0 +1,100 @@ +{ + "code": "// @filename: Component.tsx\nimport React from \"react\"\n\nexport function Hello() {\n return (\n
\n

Hello World

\n
\n )\n}\n\n// @filename: index.ts\nimport { Hello } from \"./Component\"\nconsole.log(Hello)\n", + "extension": "ts", + "highlights": [], + "queries": [], + "staticQuickInfos": [ + { + "text": "(alias) namespace React\nimport React", + "docs": "", + "start": 35, + "length": 5, + "line": 1, + "character": 7, + "targetString": "React" + }, + { + "text": "function Hello(): JSX.Element", + "docs": "", + "start": 71, + "length": 5, + "line": 3, + "character": 16, + "targetString": "Hello" + }, + { + "text": "(property) JSX.IntrinsicElements.div: React.DetailedHTMLProps, HTMLDivElement>", + "docs": "", + "start": 97, + "length": 3, + "line": 5, + "character": 5, + "targetString": "div" + }, + { + "text": "(property) JSX.IntrinsicElements.h1: React.DetailedHTMLProps, HTMLHeadingElement>", + "docs": "", + "start": 109, + "length": 2, + "line": 6, + "character": 7, + "targetString": "h1" + }, + { + "text": "(property) JSX.IntrinsicElements.h1: React.DetailedHTMLProps, HTMLHeadingElement>", + "docs": "", + "start": 125, + "length": 2, + "line": 6, + "character": 23, + "targetString": "h1" + }, + { + "text": "(property) JSX.IntrinsicElements.div: React.DetailedHTMLProps, HTMLDivElement>", + "docs": "", + "start": 135, + "length": 3, + "line": 7, + "character": 6, + "targetString": "div" + }, + { + "text": "(alias) function Hello(): JSX.Element\nimport Hello", + "docs": "", + "start": 179, + "length": 5, + "line": 12, + "character": 9, + "targetString": "Hello" + }, + { + "text": "var console: Console", + "docs": "", + "start": 206, + "length": 7, + "line": 13, + "character": 0, + "targetString": "console" + }, + { + "text": "(method) Console.log(...data: any[]): void", + "docs": "", + "start": 214, + "length": 3, + "line": 13, + "character": 8, + "targetString": "log" + }, + { + "text": "(alias) function Hello(): JSX.Element\nimport Hello", + "docs": "", + "start": 218, + "length": 5, + "line": 13, + "character": 12, + "targetString": "Hello" + } + ], + "errors": [], + "playgroundURL": "https://www.typescriptlang.org/play/#code/PTAEAEDMEsBsFMB2BDAtvAXKAwge1QA66JIAuAdKQM4AeAUNIbgE6mgBK8yAxm5M-lAAiZl15C6deDSKtQkAK6Je0YqAAS8WLFwAKAJSgA3nVChRpBc0Shdps6AA8AE2gA3AHz2HTgBYBGD01tXFAAdRZYZ0dgAK8fGNdPe306AF9JEAgYBBR0LGhEZ2lKKgYmOSMNLR1QNPkBVGFyYDwmEkRSCW5iKlwEch0Ac11gnVSgA" +} diff --git a/packages/ts-twoslasher/test/results/tests/handlesJSON.json b/packages/ts-twoslasher/test/results/tests/handlesJSON.json new file mode 100644 index 000000000000..09d844080701 --- /dev/null +++ b/packages/ts-twoslasher/test/results/tests/handlesJSON.json @@ -0,0 +1,19 @@ +{ + "code": "const i = 123\n", + "extension": "ts", + "highlights": [], + "queries": [], + "staticQuickInfos": [ + { + "text": "const i: 123", + "docs": "", + "start": 6, + "length": 1, + "line": 0, + "character": 6, + "targetString": "i" + } + ], + "errors": [], + "playgroundURL": "https://www.typescriptlang.org/play/#code/PTAEAEFsHsBMFcA2BTASsgztR8AuBLaAOwC5Qi5kAoECAM3xSIENJkyAHZgYwGtmA5sgB0AKyxEqAb1AAiFm1llZuABb4iA2aAC+NMOAZNW7UBtjIAHsNwZ9oALRPueJw6rdiGXGdABeUABGACYAZiogA" +} diff --git a/packages/typescript-vfs/CHANGELOG.md b/packages/typescript-vfs/CHANGELOG.md new file mode 100644 index 000000000000..6173df3e74b7 --- /dev/null +++ b/packages/typescript-vfs/CHANGELOG.md @@ -0,0 +1,7 @@ +### 1.2 + +Updates `createFSBackedSystem` to rely more on the default TypeScript system object which should see twoslash code samples re-using the node_modules from the local project. + +### 0.0 - 1.0 - 1.1 + +Created the lib, got it working diff --git a/packages/typescript-vfs/package.json b/packages/typescript-vfs/package.json index 5bce9f0054d0..b4e57dda575c 100755 --- a/packages/typescript-vfs/package.json +++ b/packages/typescript-vfs/package.json @@ -1,6 +1,6 @@ { "name": "@typescript/vfs", - "version": "1.1.2", + "version": "1.2.0", "license": "MIT", "author": "TypeScript team", "homepage": "https://github.com/microsoft/TypeScript-Website/", diff --git a/packages/typescript-vfs/src/index.ts b/packages/typescript-vfs/src/index.ts index 57b5730285b2..d85edf765eaa 100755 --- a/packages/typescript-vfs/src/index.ts +++ b/packages/typescript-vfs/src/index.ts @@ -324,8 +324,6 @@ export const createDefaultMapFromCDN = ( return func().then(() => fsMap) } -// TODO: Add some kind of debug logger (needs to be compat with sandbox's deployment, not just via npm) - function notImplemented(methodName: string): any { throw new Error(`Method '${methodName}' is not implemented.`) } @@ -397,48 +395,56 @@ export function createSystem(files: Map): System { * a set of virtual files which are prioritised over the FS versions, then a path to the root of your * project (basically the folder your node_modules lives) */ -export function createFSBackedSystem(files: Map, projectRoot: string): System { - const fs = require("fs") +export function createFSBackedSystem(files: Map, _projectRoot: string, ts: TS): System { + // We need to make an isolated folder for the tsconfig, but also need to be able to resolve the + // existing node_modules structures going back through the history + const root = _projectRoot + "/vfs" const path = require("path") + // The default System in TypeScript + const nodeSys = ts.sys + const tsLib = path.dirname(require.resolve("typescript")) + return { args: [], createDirectory: () => notImplemented("createDirectory"), // TODO: could make a real file tree directoryExists: audit("directoryExists", directory => { - return ( - Array.from(files.keys()).some(path => path.startsWith(directory)) || - fs.existsSync(path.join(projectRoot, directory)) - ) + return Array.from(files.keys()).some(path => path.startsWith(directory)) || nodeSys.directoryExists(directory) }), - exit: () => notImplemented("exit"), + exit: nodeSys.exit, fileExists: audit("fileExists", fileName => { if (files.has(fileName)) return true - - const fsPath = path.join(projectRoot, fileName) - const libPath = path.join(projectRoot, "node_modules", "typescript", "lib", fileName) - - for (const filepath of [fsPath, libPath]) { - if (fs.existsSync(filepath)) return true + // Don't let other tsconfigs end up touching the vfs + if (fileName.includes("tsconfig.json") || fileName.includes("tsconfig.json")) return false + if (fileName.startsWith("/lib")) { + const tsLibName = `${tsLib}/${fileName.replace("/", "")}` + return nodeSys.fileExists(tsLibName) } - return false + return nodeSys.fileExists(fileName) }), - getCurrentDirectory: () => "/", - getDirectories: () => [], + getCurrentDirectory: () => root, + getDirectories: nodeSys.getDirectories, getExecutingFilePath: () => notImplemented("getExecutingFilePath"), - readDirectory: audit("readDirectory", directory => (directory === "/" ? Array.from(files.keys()) : [])), + readDirectory: audit("readDirectory", (...args) => { + if (args[0] === "/") { + return Array.from(files.keys()) + } else { + return nodeSys.readDirectory(...args) + } + }), readFile: audit("readFile", fileName => { if (files.has(fileName)) return files.get(fileName) - - const fsPath = path.join(projectRoot, fileName) - const libPath = path.join(projectRoot, "node_modules", "typescript", "lib", fileName) - for (const filepath of [fsPath, libPath]) { - if (fs.existsSync(filepath)) return fs.readFileSync(filepath, { encoding: "utf-8" }) + if (fileName.startsWith("/lib")) { + const tsLibName = `${tsLib}/${fileName.replace("/", "")}` + return nodeSys.readFile(tsLibName) } - - return undefined + return nodeSys.readFile(fileName) }), - resolvePath: path => path, + resolvePath: path => { + if (files.has(path)) return path + return nodeSys.resolvePath(path) + }, newLine: "\n", useCaseSensitiveFileNames: true, write: () => notImplemented("write"), diff --git a/packages/typescript-vfs/test/fsbacked.test.ts b/packages/typescript-vfs/test/fsbacked.test.ts new file mode 100644 index 000000000000..d038cfd9b216 --- /dev/null +++ b/packages/typescript-vfs/test/fsbacked.test.ts @@ -0,0 +1,59 @@ +import { createFSBackedSystem, createVirtualTypeScriptEnvironment } from "../src" + +import path from "path" +import ts from "typescript" + +it("can use a FS backed system ", () => { + const compilerOpts: ts.CompilerOptions = { target: ts.ScriptTarget.ES2016, esModuleInterop: true } + const fsMap = new Map() + + const content = `/// \nimport * as path from 'path';\npath.` + fsMap.set("index.ts", content) + + const monorepoRoot = path.join(__dirname, "..", "..", "..") + const system = createFSBackedSystem(fsMap, monorepoRoot, ts) + const env = createVirtualTypeScriptEnvironment(system, ["index.ts"], ts, compilerOpts) + + const completions = env.languageService.getCompletionsAtPosition("index.ts", content.length, {}) + const hasPathJoinFunc = completions?.entries.find(c => c.name === "join") + expect(hasPathJoinFunc).toBeTruthy() +}) + +it("can use a FS backed system to extract node modules", () => { + const compilerOpts: ts.CompilerOptions = { target: ts.ScriptTarget.ES2016, esModuleInterop: true } + const fsMap = new Map() + + const content = `import React from "react"\nReact.Compone` + fsMap.set("index.ts", content) + + const monorepoRoot = path.join(__dirname, "..", "..", "..") + const system = createFSBackedSystem(fsMap, monorepoRoot, ts) + const env = createVirtualTypeScriptEnvironment(system, ["index.ts"], ts, compilerOpts) + + const completions = env.languageService.getCompletionsAtPosition("index.ts", content.length, {}) + + const hasReactComponentInCompletions = completions?.entries.find(c => c.name === "Component") + expect(hasReactComponentInCompletions).toBeTruthy() +}) + +it("can import files in the virtual fs", () => { + const compilerOpts: ts.CompilerOptions = { target: ts.ScriptTarget.ES2016, esModuleInterop: true } + const fsMap = new Map() + + const monorepoRoot = path.join(__dirname, "..", "..", "..") + const fakeFolder = path.join(monorepoRoot, "fake") + const exporter = path.join(fakeFolder, "file-with-export.ts") + const index = path.join(fakeFolder, "index.ts") + + fsMap.set(exporter, `export const helloWorld = "Example string";`) + fsMap.set(index, `import {helloWorld} from "./file-with-export"; console.log(helloWorld)`) + + const system = createFSBackedSystem(fsMap, monorepoRoot, ts) + const env = createVirtualTypeScriptEnvironment(system, [index, exporter], ts, compilerOpts) + + const errs: import("typescript").Diagnostic[] = [] + errs.push(...env.languageService.getSemanticDiagnostics(index)) + errs.push(...env.languageService.getSyntacticDiagnostics(index)) + + expect(errs.map(e => e.messageText)).toEqual([]) +}) diff --git a/packages/typescript-vfs/test/index.test.ts b/packages/typescript-vfs/test/index.test.ts index 64c3b9b3d87b..d8e69868d567 100644 --- a/packages/typescript-vfs/test/index.test.ts +++ b/packages/typescript-vfs/test/index.test.ts @@ -1,13 +1,11 @@ import { createSystem, - createFSBackedSystem, createVirtualTypeScriptEnvironment, createDefaultMapFromNodeModules, createDefaultMapFromCDN, knownLibFilesForCompilerOptions, } from "../src" -import path from "path" import ts from "typescript" it("runs a virtual environment and gets the right results from the LSP", () => { @@ -43,22 +41,6 @@ it("runs a virtual environment and gets the right results from the LSP", () => { `) }) -it("can use a FS backed system", () => { - const compilerOpts: ts.CompilerOptions = { target: ts.ScriptTarget.ES2016, esModuleInterop: true } - const fsMap = new Map() - - const content = `/// \nimport * as path from 'path';\npath.` - fsMap.set("index.ts", content) - - const monorepoRoot = path.join(__dirname, "..", "..", "..") - const system = createFSBackedSystem(fsMap, monorepoRoot) - const env = createVirtualTypeScriptEnvironment(system, ["index.ts"], ts, compilerOpts) - - const completions = env.languageService.getCompletionsAtPosition("index.ts", content.length, {}) - const hasPathJoinFunc = completions?.entries.find(c => c.name === "join") - expect(hasPathJoinFunc).toBeTruthy() -}) - // Previously lib.dom.d.ts was not included it("runs a virtual environment with the default globals", () => { const fsMap = createDefaultMapFromNodeModules({}) diff --git a/packages/typescriptlang-org/package.json b/packages/typescriptlang-org/package.json index bfe11d40ee97..4e378cc72564 100644 --- a/packages/typescriptlang-org/package.json +++ b/packages/typescriptlang-org/package.json @@ -25,7 +25,7 @@ "@mdx-js/react": "^1.5.5", "@types/node-fetch": "^2.5.3", "@types/react-helmet": "^5.0.15", - "@typescript/twoslash": "0.6.2", + "@typescript/twoslash": "1.0.0", "canvas": "^2.6.1", "gatsby": "^2.19.18", "gatsby-plugin-catch-links": "^2.1.25", From 0ab5b434d23f503f0668feb0648f1397d1bb8353 Mon Sep 17 00:00:00 2001 From: Orta Date: Tue, 25 Aug 2020 08:19:03 -0400 Subject: [PATCH 2/3] Better twoslash errors --- packages/ts-twoslasher/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ts-twoslasher/src/index.ts b/packages/ts-twoslasher/src/index.ts index ad849cef338f..d9784f307c9f 100755 --- a/packages/ts-twoslasher/src/index.ts +++ b/packages/ts-twoslasher/src/index.ts @@ -621,16 +621,16 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt if (!emitSource) { const allFiles = filenames.join(", ") - throw new Error( - `Cannot find the corresponding source file for ${emitFilename} (looking for: ${emitSourceFilename} in the vfs) - in ${allFiles}` - ) + // prettier-ignore + throw new Error(`Cannot find the corresponding source file for ${emitFilename} (looking for: ${emitSourceFilename} in the vfs) - in ${allFiles}`) } const output = ls.getEmitOutput(emitSource) const file = output.outputFiles.find(o => o.name === fsRoot + handbookOptions.showEmittedFile) if (!file) { const allFiles = output.outputFiles.map(o => o.name).join(", ") - throw new Error(`Cannot find the file ${handbookOptions.showEmittedFile} - in ${allFiles}`) + // prettier-ignore + throw new Error(`Cannot find the file ${handbookOptions.showEmittedFile} (looking for: ${fsRoot + handbookOptions.showEmittedFile} in the vfs) - in ${allFiles}`) } code = file.text From 335911c153e9075132a242dff779c8f8fd639951 Mon Sep 17 00:00:00 2001 From: Orta Date: Tue, 25 Aug 2020 08:36:43 -0400 Subject: [PATCH 3/3] Try get windows support --- .github/workflows/builds-on-windows.yml | 12 ++++++++++++ packages/ts-twoslasher/src/index.ts | 8 ++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builds-on-windows.yml b/.github/workflows/builds-on-windows.yml index 1b5bb145e599..ddc31093d3f7 100644 --- a/.github/workflows/builds-on-windows.yml +++ b/.github/workflows/builds-on-windows.yml @@ -13,6 +13,18 @@ jobs: with: node-version: "13.x" + # Cache yarn deps, to speed up CI + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - uses: actions/cache@v1 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + # Get local dependencies - run: yarn install env: diff --git a/packages/ts-twoslasher/src/index.ts b/packages/ts-twoslasher/src/index.ts index d9784f307c9f..58ca7661752f 100755 --- a/packages/ts-twoslasher/src/index.ts +++ b/packages/ts-twoslasher/src/index.ts @@ -383,11 +383,15 @@ export function twoslasher(code: string, extension: string, options: TwoSlashOpt const handbookOptions = { ...filterHandbookOptions(codeLines), ...options.defaultOptions } const compilerOptions = filterCompilerOptions(codeLines, defaultCompilerOptions, ts) + const getRoot = () => { + const path = require("path") + return process.cwd().split(path.sep).join(path.posix.sep) + } // In a browser we want to DI everything, in node we can use local infra const useFS = !!options.fsMap const vfs = useFS && options.fsMap ? options.fsMap : new Map() - const system = useFS ? createSystem(vfs) : createFSBackedSystem(vfs, process.cwd(), ts) - const fsRoot = useFS ? "/" : process.cwd() + "/" + const system = useFS ? createSystem(vfs) : createFSBackedSystem(vfs, getRoot(), ts) + const fsRoot = useFS ? "/" : getRoot() + "/" const env = createVirtualTypeScriptEnvironment(system, [], ts, compilerOptions, options.customTransformers) const ls = env.languageService