schema-dts-gen
Version:
Generate TypeScript Definitions for Schema.org Schema
105 lines • 4.16 kB
JavaScript
/**
* Copyright 2023 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import ts from 'typescript';
const { factory } = ts;
import { nameFromContext } from '../triples/term_utils.js';
export class Context {
constructor() {
this.context = [];
}
setUrlContext(ctx) {
if (this.context.length > 0) {
throw new Error('Attempting to set a default URL context, ' +
'but other named contexts exist already.');
}
this.context.push(['', ctx]);
}
addNamedContext(name, url) {
this.context.push([name, url]);
}
validate() {
if (this.context.length === 0)
throw new Error('Invalid empty context.');
if (this.context.length === 1)
return; // One context is always right.
// Make sure no key is duplicated.
const seen = new Set();
for (const [name] of this.context) {
if (seen.has(name)) {
throw new Error(`Named context ${name} found twice in context.`);
}
if (name === '') {
throw new Error('Context with multiple named contexts includes unnamed URL.');
}
seen.add(name);
}
}
getScopedName(node) {
for (const [ctxName, url] of this.context) {
const name = nameFromContext(node, url);
if (name !== null) {
// Valid possibilities:
// - "schema:Foo" when name == schema && node.name == Foo.
// - "schema:" when name == schema && node.name is undefined.
// - "Foo" when name is empty and node.name is Foo.
//
// Don't allow "" when name is empty and node.name is undefined.
return ctxName === '' ? name || node.id : `${ctxName}:${name}`;
}
}
return node.id;
}
typeNode() {
// Either:
// "@context": "https://schema.org" or similar:
if (this.context.length === 1 && this.context[0][0] === '') {
return factory.createLiteralTypeNode(factory.createStringLiteral(this.context[0][1]));
}
// or "@context" is a literal object type of key-values of URLs:
return factory.createTypeLiteralNode(
/*members=*/ this.context.map(([name, url]) => factory.createPropertySignature(
/*modifiers=*/ [], factory.createStringLiteral(name),
/*questionToken=*/ undefined, factory.createLiteralTypeNode(factory.createStringLiteral(url)))));
}
/** Creates the full property signature for "@context" given the configured context. */
contextProperty() {
return factory.createPropertySignature(
/*modifiers=*/ [], factory.createStringLiteral('@context'),
/*questionToken=*/ undefined, this.typeNode());
}
static Parse(contextSpec) {
const keyVals = contextSpec
.split(',')
.map(s => s.trim())
.filter(s => !!s);
const context = new Context();
if (keyVals.length === 1) {
context.setUrlContext(keyVals[0]);
}
else {
for (const keyVal of keyVals) {
const match = /^([^:]+):((http|https):.+)$/g.exec(keyVal);
if (!match || match[1] === undefined || match[2] === undefined) {
throw new Error(`Unknown value ${keyVal} in --context flag.`);
}
context.addNamedContext(match[1], match[2]);
}
}
context.validate();
return context;
}
}
//# sourceMappingURL=context.js.map