Support specifying top-levels explicitly in TS input

If there are types that have doc comments containing
`#TopLevel`, then only those types will be made top-levels.
This commit is contained in:
Mark Probst 2018-04-08 20:13:09 -07:00
Родитель dd3f5e22eb
Коммит cc1dabedc4
3 изменённых файлов: 97 добавлений и 37 удалений

Просмотреть файл

@ -46,7 +46,7 @@ function isTypeScriptSource(source: TypeSource): source is TypeScriptTypeSource
export interface SchemaTypeSource {
kind: "schema";
name: string;
uri?: string;
uris?: string[];
schema?: StringInput;
}
@ -58,12 +58,8 @@ function toSchemaSource(source: TypeSource): [SchemaTypeSource, boolean] | undef
if (isSchemaSource(source)) {
return [source, true];
} else if (isTypeScriptSource(source)) {
return [{
kind: "schema",
name: "",
schema: schemaForTypeScriptSources(source.sources),
uri: "#/definitions/"
}, false];
const { schema, name, uris } = schemaForTypeScriptSources(source.sources);
return [{ kind: "schema", name, schema, uris }, false];
}
return undefined;
}
@ -89,7 +85,9 @@ class InputJSONSchemaStore extends JSONSchemaStore {
async fetch(address: string): Promise<JSONSchema | undefined> {
const maybeInput = this._inputs.get(address);
if (maybeInput !== undefined) {
return checkJSONSchema(parseJSON(await toString(maybeInput), "JSON Schema", address), () => Ref.root(address));
return checkJSONSchema(parseJSON(await toString(maybeInput), "JSON Schema", address), () =>
Ref.root(address)
);
}
if (this._delegate === undefined) {
return panic(`Schema URI ${address} requested, but no store given`);
@ -106,8 +104,10 @@ export class InputData {
private _schemaInputs: Map<string, StringInput> = Map();
private _schemaSources: List<[uri.URI, SchemaTypeSource]> = List();
constructor(private readonly _compressedJSON: CompressedJSON, private readonly _givenSchemaStore: JSONSchemaStore | undefined) {
}
constructor(
private readonly _compressedJSON: CompressedJSON,
private readonly _givenSchemaStore: JSONSchemaStore | undefined
) {}
get jsonInputs(): Map<string, { samples: Value[]; description?: string }> {
return Map(this._samples);
@ -153,32 +153,44 @@ export class InputData {
}
private addSchemaTypeSource(schemaSource: SchemaTypeSource): void {
const { uri, schema } = schemaSource;
const { uris, schema } = schemaSource;
let normalizedURI: uri.URI;
let normalizedURIs: uri.URI[];
const uriPath = `-${this._schemaInputs.size + 1}`;
if (uri === undefined) {
normalizedURI = new URI(uriPath);
if (uris === undefined) {
normalizedURIs = [new URI(uriPath)];
} else {
normalizedURI = new URI(uri).normalize();
if (normalizedURI.clone().hash("").toString() === "") {
normalizedURI.path(uriPath);
}
normalizedURIs = uris.map(uri => {
const normalizedURI = new URI(uri).normalize();
if (
normalizedURI
.clone()
.hash("")
.toString() === ""
) {
normalizedURI.path(uriPath);
}
return normalizedURI;
});
}
if (schema === undefined) {
assert(uri !== undefined, "URI must be given if schema source is not specified");
assert(uris !== undefined, "URIs must be given if schema source is not specified");
} else {
this._schemaInputs = this._schemaInputs.set(
normalizedURI
.clone()
.hash("")
.toString(),
schema
);
for (const normalizedURI of normalizedURIs) {
this._schemaInputs = this._schemaInputs.set(
normalizedURI
.clone()
.hash("")
.toString(),
schema
);
}
}
this._schemaSources = this._schemaSources.push([normalizedURI, schemaSource]);
for (const normalizedURI of normalizedURIs) {
this._schemaSources = this._schemaSources.push([normalizedURI, schemaSource]);
}
}
// Returns whether we need IR for this type source
@ -191,10 +203,10 @@ export class InputData {
if (maybeSchemaSource !== undefined) {
const [schemaSource, isDirectInput] = maybeSchemaSource;
needIR = isDirectInput || needIR;
this.addSchemaTypeSource(schemaSource);
} else {
needIR = await this.addOtherTypeSource(source) || needIR;
} else {
needIR = (await this.addOtherTypeSource(source)) || needIR;
continue;
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import * as ts from "typescript";
import { PartialArgs, CompilerOptions, generateSchema } from "typescript-json-schema";
import { panic, inflateBase64 } from "./Support";
import { panic, inflateBase64, defined } from "./Support";
import { encodedDefaultTypeScriptLibrary } from "./EncodedDefaultTypeScriptLibrary";
import { ErrorMessage, messageError } from "./Messages";
@ -94,9 +94,15 @@ class CompilerHost implements ts.CompilerHost {
}
}
export function schemaForTypeScriptSources(sourceFileNames: string[]): string;
export function schemaForTypeScriptSources(sources: { [fileName: string]: string }): string;
export function schemaForTypeScriptSources(sources: string[] | { [fileName: string]: string }): string {
// FIXME: We're stringifying and then parsing this schema again. Just pass around
// the schema directly.
export function schemaForTypeScriptSources(sourceFileNames: string[]): { schema: string; name: string; uris: string[] };
export function schemaForTypeScriptSources(sources: {
[fileName: string]: string;
}): { schema: string; name: string; uris: string[] };
export function schemaForTypeScriptSources(
sources: string[] | { [fileName: string]: string }
): { schema: string; name: string; uris: string[] } {
let fileNames: string[];
let host: ts.CompilerHost;
@ -118,5 +124,47 @@ export function schemaForTypeScriptSources(sources: string[] | { [fileName: stri
}
const schema = generateSchema(program, "*", settings);
return JSON.stringify(schema);
const uris: string[] = [];
let topLevelName: string | undefined = undefined;
if (schema !== null && typeof schema === "object" && typeof schema.definitions === "object") {
for (const name of Object.getOwnPropertyNames(schema.definitions)) {
const definition = schema.definitions[name];
if (
definition === null ||
Array.isArray(definition) ||
typeof definition !== "object" ||
typeof definition.description !== "string"
) {
continue;
}
const description = definition.description as string;
const matches = description.match(/#TopLevel/);
if (matches === null) {
continue;
}
const index = defined(matches.index);
definition.description = description.substr(0, index) + description.substr(index + matches[0].length);
uris.push(`#/definitions/${name}`);
if (topLevelName === undefined) {
if (typeof definition.title === "string") {
topLevelName = definition.title;
} else {
topLevelName = name;
}
} else {
topLevelName = "";
}
}
}
if (uris.length === 0) {
uris.push("#/definitions/");
}
if (topLevelName === undefined) {
topLevelName = "";
}
return { schema: JSON.stringify(schema), name: topLevelName, uris };
}

Просмотреть файл

@ -119,7 +119,7 @@ async function samplesFromDirectory(dataDir: string): Promise<TypeSource[]> {
sourcesInDir.push({
kind: "schema",
name,
uri: fileOrUrl
uris: [fileOrUrl]
});
} else if (file.endsWith(".gqlschema")) {
messageAssert(graphQLSchema === undefined, ErrorMessage.DriverMoreThanOneGraphQLSchemaInDir, {
@ -603,7 +603,7 @@ async function typeSourcesForURIs(name: string, uris: string[], options: CLIOpti
case "json":
return [await sourceFromFileOrUrlArray(name, uris)];
case "schema":
return uris.map(uri => ({ kind: "schema", name, uri } as SchemaTypeSource));
return uris.map(uri => ({ kind: "schema", name, uris: [uri] } as SchemaTypeSource));
default:
return panic(`typeSourceForURIs must not be called for source language ${options.srcLang}`);
}