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
6 changes: 6 additions & 0 deletions .changeset/itchy-pumas-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@typescript/twoslash": patch
"@typescript/sandbox": patch
---

Support twoslash directives for `TsConfigOnlyOption` and its list
5 changes: 5 additions & 0 deletions .changeset/purple-goats-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@typescript/ata": patch
---

Internal typing improvements
4 changes: 2 additions & 2 deletions packages/ata/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,8 @@ export const getReferencesForModule = (ts: typeof import("typescript"), code: st
const meta = ts.preProcessFile(code)

// Ensure we don't try download TypeScript lib references
// @ts-ignore - private but likely to never change
const libMap: Map<string, string> = ts.libMap || new Map()
// private but likely to never change
const libMap = ts.libMap || new Map()

// TODO: strip /// <reference path='X' />?

Expand Down
1 change: 1 addition & 0 deletions packages/ata/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"include": ["../ts-internals"],
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
Expand Down
18 changes: 6 additions & 12 deletions packages/playground/src/createConfigDropdown.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { CommandLineOption, CommandLineOptionOfBooleanType } from "typescript"

type Sandbox = import("@typescript/sandbox").Sandbox
type Monaco = typeof import("monaco-editor")

Expand All @@ -12,14 +14,6 @@ type OptionsSummary = {
// This is where all the localized descriptions come from
declare const optionsSummary: OptionsSummary[]

type CompilerOptStub = {
name: string
type: string
isCommandLineOnly: boolean
category: any
description: any
}

const notCompilerOptions = ["Project_Files_0", "Watch_Options_999", "Command_line_Options_6171"]
const notRelevantToPlayground = [
"listFiles",
Expand Down Expand Up @@ -56,15 +50,15 @@ export const createConfigDropdown = (sandbox: Sandbox, monaco: Monaco) => {
container.id = "boolean-options-container"
configContainer.appendChild(container)

// @ts-ignore
const allOptions: CompilerOptStub[] = sandbox.ts.optionDeclarations
const allOptions = sandbox.ts.optionDeclarations

const boolOptions = allOptions.filter(
k =>
(k): k is CommandLineOptionOfBooleanType & Required<Pick<CommandLineOption, "category" | "description">> =>
!notRelevantToPlayground.includes(k.name) &&
k.type === "boolean" &&
!k.isCommandLineOnly &&
k.category &&
!!k.category &&
!!k.description &&
!notCompilerOptions.includes(k.category.key)
)

Expand Down
4 changes: 2 additions & 2 deletions packages/playground/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"include": ["src"],
"include": ["src", "../ts-internals"],
"compilerOptions": {
"outDir": "../typescriptlang-org/static/js/playground",
"jsx": "react",
Expand All @@ -13,6 +13,6 @@
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"skipLibCheck": true
}
}
2 changes: 1 addition & 1 deletion packages/sandbox/src/compilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function getDefaultSandboxCompilerOptions(
target: monaco.languages.typescript.ScriptTarget.ES2017,
jsx: monaco.languages.typescript.JsxEmit.React,
module: monaco.languages.typescript.ModuleKind.ESNext,
baseUrl: "file:///",
}

if (major >= 5) {
Expand Down Expand Up @@ -94,7 +95,6 @@ export const getCompilerOptionsFromParams = (
if (toSet !== undefined) returnedOptions[key] = toSet
} else {
// If that doesn't work, double check that the flag exists and allow it through
// @ts-ignore
const flagExists = ts.optionDeclarations.find(opt => opt.name === key)
if (flagExists) {
let realValue: number | boolean = true
Expand Down
65 changes: 47 additions & 18 deletions packages/sandbox/src/twoslashSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ const booleanConfigRegexp = /^\/\/\s?@(\w+)$/
const valuedConfigRegexp = /^\/\/\s?@(\w+):\s?(.+)$/

type TS = typeof import("typescript")
type CompilerOptions = import("typescript").CompilerOptions
type CompilerOptions = import("monaco-editor").languages.typescript.CompilerOptions
type CommandLineOption = import("typescript").CommandLineOption

/**
* This is a port of the twoslash bit which grabs compiler options
* from the source code
*/

export const extractTwoSlashCompilerOptions = (ts: TS) => {
let optMap = new Map<string, any>()
const optMap = new Map<string, CommandLineOption>()

if (!("optionDeclarations" in ts)) {
console.error("Could not get compiler options from ts.optionDeclarations - skipping twoslash support.")
} else {
// @ts-ignore - optionDeclarations is not public API
for (const opt of ts.optionDeclarations) {
optMap.set(opt.name.toLowerCase(), opt)
}
}

return (code: string) => {
const codeLines = code.split("\n")
const options = {} as any
const options: CompilerOptions = {}

codeLines.forEach(_line => {
let match
Expand All @@ -45,7 +45,7 @@ export const extractTwoSlashCompilerOptions = (ts: TS) => {
}
}

function setOption(name: string, value: string, opts: CompilerOptions, optMap: Map<string, any>) {
function setOption(name: string, value: string, opts: CompilerOptions, optMap: Map<string, CommandLineOption>) {
const opt = optMap.get(name.toLowerCase())

if (!opt) return
Expand All @@ -57,27 +57,50 @@ function setOption(name: string, value: string, opts: CompilerOptions, optMap: M
break

case "list":
const elementType = opt.element!.type
case "listOrElement":
const elementType = opt.element.type
const strings = value.split(",")
if (typeof elementType === "string") {
opts[opt.name] = strings.map(v => parsePrimitive(v, elementType))
} else {
opts[opt.name] = strings.map(v => getOptionValueFromMap(opt.name, v, elementType as Map<string, string>)!).filter(Boolean)
switch (elementType) {
case "string":
case "number":
opts[opt.name] = strings.map(v => parsePrimitive(v, elementType))
break
case "object":
opts[opt.name] = strings.map(v => parseObject(v, opt.name))
break
case "boolean":
console.log(`List of ${elementType} is not yet supported.`)
break
default:
opts[opt.name] = strings
.map(v => getOptionValueFromMap(opt.name, v, elementType))
.filter(v => v !== undefined)
}
break

default: // It's a map!
const optMap = opt.type as Map<string, string>
case "object":
opts[opt.name] = parseObject(value, opt.name)
break
default: // It's a map!
const optMap = opt.type
opts[opt.name] = getOptionValueFromMap(opt.name, value, optMap)
}

if (opts[opt.name] === undefined) {
const keys = Array.from(opt.type.keys() as any)
if (opts[opt.name] === undefined && opt.type instanceof Map) {
const keys = Array.from(opt.type.keys())
console.log(`Invalid value ${value} for ${opt.name}. Allowed values: ${keys.join(",")}`)
}
}

export function parsePrimitive(value: string, type: string): any {
export function parsePrimitive<T extends string>(
value: string,
type: T
): {
number: number
string: string
boolean: boolean
[type: string]: number | string | boolean | undefined
}[T] {
switch (type) {
case "number":
return +value
Expand All @@ -89,11 +112,18 @@ export function parsePrimitive(value: string, type: string): any {
console.log(`Unknown primitive type ${type} with - ${value}`)
}

function parseObject(value: string, name: string) {
try {
return JSON.parse(value)
} catch {
console.log(`Invalid JSON value ${value} for ${name}.`)
}
}

function getOptionValueFromMap(name: string, key: string, optMap: Map<string, string>) {
function getOptionValueFromMap(name: string, key: string, optMap: Map<string, string | number>) {
const result = optMap.get(key.toLowerCase())
if (result === undefined) {
const keys = Array.from(optMap.keys() as any)
const keys = Array.from(optMap.keys())

console.error(
`Invalid inline compiler value`,
Expand Down Expand Up @@ -161,7 +191,6 @@ export const twoslashCompletions = (ts: TS, monaco: typeof import("monaco-editor
"noErrorValidation",
"filename",
]
// @ts-ignore - ts.optionDeclarations is private
const optsNames = ts.optionDeclarations.map(o => o.name)
knowns.concat(optsNames).forEach(name => {
if (name.startsWith(word.slice(1))) {
Expand Down
2 changes: 1 addition & 1 deletion packages/sandbox/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"include": ["src", "src/vendor/lzstring.min.js"],
"include": ["src", "src/vendor/lzstring.min.js", "../ts-internals"],

"compilerOptions": {
"outDir": "../typescriptlang-org/static/js/sandbox",
Expand Down
127 changes: 127 additions & 0 deletions packages/ts-internals/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import "typescript"

// These are all copy-pasted from https://github.com/microsoft/TypeScript/tree/be8678315541e814da14316848a9468e8f90ab11
declare module "typescript" {
/** @internal */
const optionDeclarations: CommandLineOption[]
/** @internal */
const optionsForWatch: CommandLineOption[]
/** @internal */
export const commonOptionsWithBuild: CommandLineOption[]
/** @internal */
export const buildOpts: CommandLineOption[]
/** @internal */
const typeAcquisitionDeclarations: CommandLineOption[]
/** @internal */
const defaultInitCompilerOptions: CompilerOptions
/**
* A map of lib names to lib files. This map is used both for parsing the "lib" command line
* option as well as for resolving lib reference directives.
*
* @internal
*/
export const libMap: Map<string, string>

/** @internal */
export interface OptionsNameMap {
optionsNameMap: Map<string, CommandLineOption>
shortOptionNames: Map<string, string>
}

// prettier-ignore
/** @internal */
export interface CommandLineOptionBase {
name: string;
type: "string" | "number" | "boolean" | "object" | "list" | "listOrElement" | Map<string, number | string>; // a value of a primitive type, or an object literal mapping named values to actual values
isFilePath?: boolean; // True if option value is a path or fileName
shortName?: string; // A short mnemonic for convenience - for instance, 'h' can be used in place of 'help'
description?: DiagnosticMessage; // The message describing what the command line switch does.
defaultValueDescription?: string | number | boolean | DiagnosticMessage | undefined; // The message describing what the dafault value is. string type is prepared for fixed chosen like "false" which do not need I18n.
paramType?: DiagnosticMessage; // The name to be used for a non-boolean option's parameter
isTSConfigOnly?: boolean; // True if option can only be specified via tsconfig.json file
isCommandLineOnly?: boolean;
showInSimplifiedHelpView?: boolean;
category?: DiagnosticMessage;
strictFlag?: true; // true if the option is one of the flag under strict
allowJsFlag?: true;
affectsSourceFile?: true; // true if we should recreate SourceFiles after this option changes
affectsModuleResolution?: true; // currently same effect as `affectsSourceFile`
affectsBindDiagnostics?: true; // true if this affects binding (currently same effect as `affectsSourceFile`)
affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics
affectsEmit?: true; // true if the options affects emit
affectsProgramStructure?: true; // true if program should be reconstructed from root files if option changes and does not affect module resolution as affectsModuleResolution indirectly means program needs to reconstructed
affectsDeclarationPath?: true; // true if the options affects declaration file path computed
affectsBuildInfo?: true; // true if this options should be emitted in buildInfo
transpileOptionValue?: boolean | undefined; // If set this means that the option should be set to this value when transpiling
extraValidation?: (value: CompilerOptionsValue) => [DiagnosticMessage, ...string[]] | undefined; // Additional validation to be performed for the value to be valid
disallowNullOrUndefined?: true; // If set option does not allow setting null
allowConfigDirTemplateSubstitution?: true; // If set option allows substitution of `${configDir}` in the value
}

/** @internal */
export interface CommandLineOptionOfStringType extends CommandLineOptionBase {
type: "string"
defaultValueDescription?: string | undefined | DiagnosticMessage
}

/** @internal */
export interface CommandLineOptionOfNumberType extends CommandLineOptionBase {
type: "number"
defaultValueDescription: number | undefined | DiagnosticMessage
}

/** @internal */
export interface CommandLineOptionOfBooleanType extends CommandLineOptionBase {
type: "boolean"
defaultValueDescription: boolean | undefined | DiagnosticMessage
}

/** @internal */
export interface CommandLineOptionOfCustomType extends CommandLineOptionBase {
type: Map<string, number | string> // an object literal mapping named values to actual values
defaultValueDescription: number | string | undefined | DiagnosticMessage
deprecatedKeys?: Set<string>
}

/** @internal */
export interface AlternateModeDiagnostics {
diagnostic: DiagnosticMessage
getOptionsNameMap: () => OptionsNameMap
}

/** @internal */
export interface DidYouMeanOptionsDiagnostics {
alternateMode?: AlternateModeDiagnostics
optionDeclarations: CommandLineOption[]
unknownOptionDiagnostic: DiagnosticMessage
unknownDidYouMeanDiagnostic: DiagnosticMessage
}

/** @internal */
export interface TsConfigOnlyOption extends CommandLineOptionBase {
type: "object"
elementOptions?: Map<string, CommandLineOption>
extraKeyDiagnostics?: DidYouMeanOptionsDiagnostics
}

/** @internal */
export interface CommandLineOptionOfListType extends CommandLineOptionBase {
type: "list" | "listOrElement"
element:
| CommandLineOptionOfCustomType
| CommandLineOptionOfStringType
| CommandLineOptionOfNumberType
| CommandLineOptionOfBooleanType
| TsConfigOnlyOption
listPreserveFalsyValues?: boolean
}

/** @internal */
export type CommandLineOption =
| CommandLineOptionOfCustomType
| CommandLineOptionOfStringType
| CommandLineOptionOfNumberType
| CommandLineOptionOfBooleanType
| TsConfigOnlyOption
| CommandLineOptionOfListType
}
12 changes: 12 additions & 0 deletions packages/ts-internals/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "ts-internals",
"private": true,
"license": "MIT",
"version": "1.0.0",
"scripts": {
"build": "tsc --build ."
},
"devDependencies": {
"typescript": "*"
}
}
10 changes: 10 additions & 0 deletions packages/ts-internals/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"target": "ES5",
"strict": true,
"skipLibCheck": false,
"noEmit": true
}
}
Loading