Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

google / schema-dts / 110

14 Jan 2020 - 21:05 coverage decreased (-1.9%) to 78.721%
110

push

travis-ci

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Merge pull request #59 from Eyas/better-coverage

Better coverage

212 of 295 branches covered (71.86%)

Branch coverage included in aggregate %.

465 of 565 relevant lines covered (82.3%)

43.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.5
/src/ts/class.ts
1
/**
2
 * Copyright 2018 Google LLC
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
import {createAsExpression, createIntersectionTypeNode, createLiteralTypeNode, createModifiersFromModifierFlags, createObjectLiteral, createParenthesizedType, createPropertyAssignment, createStringLiteral, createTypeAliasDeclaration, createTypeLiteralNode, createTypeReferenceNode, createUnionTypeNode, createVariableDeclaration, createVariableDeclarationList, createVariableStatement, DeclarationStatement, ModifierFlags, NodeFlags, Statement, TypeAliasDeclaration, TypeNode, VariableStatement} from 'typescript';
1×
17

18
import {Log} from '../logging';
1×
19
import {TObject, TPredicate, TSubject} from '../triples/triple';
20
import {UrlNode} from '../triples/types';
1×
21
import {GetComment, GetSubClassOf, IsSupersededBy} from '../triples/wellKnown';
1×
22

23
import {Context} from './context';
24
import {EnumValue} from './enum';
25
import {IdPropertyNode, Property, TypeProperty} from './property';
1×
26
import {arrayOf} from './util/arrayof';
1×
27
import {withComments} from './util/comments';
1×
28
import {toClassName} from './util/names';
1×
29

30
/** Maps fully qualified IDs of each Class to the class itself. */
31
export type ClassMap = Map<string, Class>;
32

33
/**
34
 * Represents a "Class" in Schema.org, except in cases where it is better
35
 * described by Builtin (i.e. is a DataType).
36
 *
37
 * In TypeScript, this corresponds to a collection of declarations:
38
 * 1. If the class has enum values, an Enum declaration.
39
 * 2. If the class has properties, the properties in an object literal.
40
 * 3. If the class has children,
41
 *        a type union over all children.
42
 *    otherwise, a "type" property.
43
 */
44
export class Class {
1×
45
  private _comment?: string;
46
  private readonly children: Class[] = [];
37×
47
  private readonly parents: Class[] = [];
37×
48
  private readonly _props: Set<Property> = new Set();
37×
49
  private readonly _enums: Set<EnumValue> = new Set();
37×
50
  private readonly _supersededBy: Set<Class> = new Set();
37×
51

52
  private inheritsDataType(): boolean {
53
    for (const parent of this.parents) {
58×
54
      if (parent instanceof Builtin || parent.inheritsDataType()) {
29×
55
        return true;
1×
56
      }
57
    }
58
    return false;
57×
59
  }
60

61
  get deprecated() {
62
    return this._supersededBy.size > 0;
137×
63
  }
64

65
  private get comment() {
66
    if (!this.deprecated) return this._comment;
Branches [[2, 1]] missed. 30×
67
    const deprecated = `@deprecated Use ${
!
68
        this.supersededBy().map(c => c.className()).join(' or ')} instead.`;
!
69
    return this._comment ? `${this._comment}\n${deprecated}` : deprecated;
Branches [[3, 0], [3, 1]] missed. !
70
  }
71

72
  private properties() {
73
    return Array.from(this._props.values())
30×
74
        .sort((a, b) => CompareKeys(a.key, b.key));
13×
75
  }
76

77
  private supersededBy() {
78
    return Array.from(this._supersededBy)
!
79
        .sort((a, b) => CompareKeys(a.subject, b.subject));
!
80
  }
81

82
  private enums() {
83
    return Array.from(this._enums)
2×
84
        .sort((a, b) => CompareKeys(a.value, b.value));
10×
85
  }
86

87
  private get allowString(): boolean {
88
    return this._allowStringType ||
39×
89
        this.parents.some(parent => parent.allowString);
9×
90
  }
91

92
  protected baseName() {
93
    return toClassName(this.subject) + 'Base';
79×
94
  }
95
  private className() {
96
    return toClassName(this.subject);
50×
97
  }
98

99
  constructor(
100
      readonly subject: TSubject, private readonly _allowStringType: boolean) {}
37×
101
  add(value: {Predicate: TPredicate; Object: TObject},
102
      classMap: ClassMap): boolean {
103
    const c = GetComment(value);
22×
104
    if (c) {
22×
105
      if (this._comment) {
Branches [[6, 0]] missed. 2×
106
        Log(`Duplicate comments provided on class ${
!
107
            this.subject.toString()}. It will be overwritten.`);
108
      }
109
      this._comment = c.comment;
2×
110
      return true;
2×
111
    }
112
    const s = GetSubClassOf(value);
20×
113
    if (s) {
Branches [[7, 1]] missed. 20×
114
      const parentClass = classMap.get(s.subClassOf.toString());
20×
115
      if (parentClass) {
Branches [[8, 1]] missed. 20×
116
        this.parents.push(parentClass);
20×
117
        parentClass.children.push(this);
20×
118
      } else {
119
        throw new Error(`Couldn't find parent of ${this.subject.name}, ${
!
120
            s.subClassOf.toString()}`);
121
      }
122
      return true;
20×
123
    }
124

125
    if (IsSupersededBy(value.Predicate)) {
Branches [[9, 0], [9, 1]] missed. !
126
      const supersededBy = classMap.get(value.Object.toString());
!
127
      if (!supersededBy) {
Branches [[10, 0], [10, 1]] missed. !
128
        throw new Error(`Couldn't find class ${
!
129
            value.Object.toString()}, which supersedes class ${
130
            this.subject.name}`);
131
      }
132
      this._supersededBy.add(supersededBy);
!
133
      return true;
!
134
    }
135

136
    return false;
!
137
  }
138
  addProp(p: Property) {
139
    this._props.add(p);
25×
140
  }
141
  addEnum(e: EnumValue) {
142
    this._enums.add(e);
4×
143
  }
144

145
  private baseNode(skipDeprecatedProperties: boolean, context: Context):
146
      TypeNode {
147
    const parentTypes = this.parents.map(
30×
148
        parent => createTypeReferenceNode(parent.baseName(), []));
20×
149
    const parentNode = parentTypes.length === 0 ?
30×
150
        null :
151
        parentTypes.length === 1 ?
152
        parentTypes[0] :
Branches [[12, 1]] missed.
153
        createParenthesizedType(createIntersectionTypeNode(parentTypes));
154

155
    const isRoot = parentNode === null;
30×
156

157
    // Properties part.
158
    const propLiteral = createTypeLiteralNode([
30×
159
      // Add an '@id' property for the root.
160
      ...(isRoot ? [IdPropertyNode()] : []),
161
      // ... then everything else.
162
      ...this.properties()
163
          .filter(property => !property.deprecated || !skipDeprecatedProperties)
Branches [[14, 1]] missed. 25×
164
          .map(prop => prop.toNode(context))
25×
165
    ]);
166

167
    if (parentNode && propLiteral.members.length > 0) {
30×
168
      return createIntersectionTypeNode([parentNode, propLiteral]);
7×
169
    } else if (parentNode) {
23×
170
      return parentNode;
13×
171
    } else if (propLiteral.members.length > 0) {
Branches [[18, 1]] missed. 10×
172
      return propLiteral;
10×
173
    } else {
174
      return createTypeLiteralNode([]);
!
175
    }
176
  }
177

178
  private baseDecl(skipDeprecatedProperties: boolean, context: Context):
179
      TypeAliasDeclaration {
180
    const baseNode = this.baseNode(skipDeprecatedProperties, context);
30×
181

182
    return createTypeAliasDeclaration(
30×
183
        /*decorators=*/[], /*modifiers=*/[], this.baseName(),
184
        /*typeParameters=*/[], baseNode);
185
  }
186

187
  private nonEnumType(context: Context): TypeNode {
188
    this.children.sort((a, b) => CompareKeys(a.subject, b.subject));
30×
189
    const children = this.children.map(
30×
190
        child =>
191
            createTypeReferenceNode(child.className(), /*typeArguments=*/[]));
19×
192

193
    // 'String' is a valid Type sometimes, add that as a Child if so.
194
    if (this.allowString) {
30×
195
      children.push(createTypeReferenceNode('string', /*typeArguments=*/[]));
15×
196
    }
197

198
    const childrenNode = children.length === 0 ?
30×
199
        null :
200
        children.length === 1 ?
201
        children[0] :
202
        createParenthesizedType(createUnionTypeNode(children));
203

204
    const baseTypeReference =
205
        createTypeReferenceNode(this.baseName(), /*typeArguments=*/[]);
30×
206

207
    // If we inherit from a DataType (~= a Built In), then the type is _not_
208
    // represented as a node. Skip the leaf type.
209
    const thisType = this.inheritsDataType() ?
30×
210
        baseTypeReference :
211
        createIntersectionTypeNode([
212
          createTypeLiteralNode(
213
              [new TypeProperty(this.subject).toNode(context)]),
214
          baseTypeReference
215
        ]);
216

217
    if (childrenNode) {
30×
218
      return createUnionTypeNode([thisType, childrenNode]);
21×
219
    } else {
220
      return thisType;
9×
221
    }
222
  }
223

224
  private totalType(context: Context): TypeNode {
225
    const isEnum = this._enums.size > 0;
30×
226

227
    if (isEnum) {
30×
228
      return createUnionTypeNode([
1×
229
        ...this.enums().map(e => e.toTypeLiteral()),
4×
230
        createParenthesizedType(this.nonEnumType(context)),
231
      ]);
232
    } else {
233
      return this.nonEnumType(context);
29×
234
    }
235
  }
236

237
  private enumDecl(): VariableStatement|undefined {
238
    if (this._enums.size === 0) return undefined;
30×
239
    const enums = this.enums();
1×
240

241
    return createVariableStatement(
1×
242
        createModifiersFromModifierFlags(ModifierFlags.Export),
243
        createVariableDeclarationList(
244
            [createVariableDeclaration(
245
                this.className(),
246
                /*type=*/undefined,
247
                createObjectLiteral(
248
                    enums.map(e => e.toNode()), /*multiLine=*/true))],
4×
249
            NodeFlags.Const));
250
  }
251

252
  toNode(context: Context, skipDeprecatedProperties: boolean):
253
      readonly Statement[] {
254
    const typeValue: TypeNode = this.totalType(context);
30×
255
    const declaration = withComments(
30×
256
        this.comment,
257
        createTypeAliasDeclaration(
258
            /* decorators = */[],
259
            createModifiersFromModifierFlags(ModifierFlags.Export),
260
            this.className(),
261
            [],
262
            typeValue,
263
            ));
264

265
    // Guide to Code Generated:
266
    // // Base: Always There -----------------------//
267
    // type XyzBase = (Parents) & {
268
    //   ... props;
269
    // };
270
    // // Complete Type ----------------------------//
271
    // export type Xyz = "Enum1"|"Enum2"|...        // Enum Piece: Optional.
272
    //                  |XyzBase&{'@type': 'Xyz'}   // 'Leaf' Piece.
273
    //                  |Child1|Child2|...          // Child Piece: Optional.
274
    // // Enum Values: Optional --------------------//
275
    // export const Xyz = {
276
    //   Enum1 = "Enum1" as const,
277
    //   Enum2 = "Enum2" as const,
278
    //   ...
279
    // }
280
    // //-------------------------------------------//
281
    return arrayOf<Statement>(
30×
282
        this.baseDecl(skipDeprecatedProperties, context),
283
        declaration,
284
        this.enumDecl(),
285
    );
286
  }
287
}
288

289
/**
290
 * Represents a DataType. A "Native" Schema.org object that is best represented
291
 * in JSON-LD and JavaScript as a typedef to a native type.
292
 */
293
export class Builtin extends Class {
1×
294
  constructor(
295
      url: string, private readonly equivTo: string,
7×
296
      protected readonly doc: string) {
7×
297
    super(UrlNode.Parse(url), false);
7×
298
  }
299

300
  toNode(): readonly Statement[] {
301
    return [
55×
302
      withComments(
303
          this.doc,
304
          createTypeAliasDeclaration(
305
              /*decorators=*/[],
306
              createModifiersFromModifierFlags(ModifierFlags.Export),
307
              this.subject.name,
308
              /*typeParameters=*/[],
309
              createTypeReferenceNode(this.equivTo, []))),
310
    ];
311
  }
312

313
  protected baseName() {
314
    return this.subject.name;
1×
315
  }
316
}
317
export class BooleanEnum extends Builtin {
1×
318
  constructor(
319
      url: string, private trueUrl: string, private falseUrl: string,
1×
320
      doc: string) {
321
    super(url, '', doc);
1×
322
  }
323

324
  toNode(): readonly Statement[] {
325
    return [
11×
326
      withComments(
327
          this.doc,
328
          createTypeAliasDeclaration(
329
              /*decotrators=*/[],
330
              createModifiersFromModifierFlags(ModifierFlags.Export),
331
              this.subject.name,
332
              /*typeParameters=*/[],
333
              createUnionTypeNode([
334
                createTypeReferenceNode('true', /*typeArgs=*/[]),
335
                createTypeReferenceNode('false', /*typeArgs=*/[]),
336
                createLiteralTypeNode(createStringLiteral(this.trueUrl)),
337
                createLiteralTypeNode(createStringLiteral(this.falseUrl)),
338
              ]),
339
              )),
340
      createVariableStatement(
341
          createModifiersFromModifierFlags(ModifierFlags.Export),
342
          createVariableDeclarationList(
343
              [createVariableDeclaration(
344
                  this.subject.name,
345
                  /*type=*/undefined,
346
                  createObjectLiteral(
347
                      [
348
                        createPropertyAssignment(
349
                            'True',
350
                            createAsExpression(
351
                                createStringLiteral(this.trueUrl),
352
                                createTypeReferenceNode('const', undefined))),
353
                        createPropertyAssignment(
354
                            'False',
355
                            createAsExpression(
356
                                createStringLiteral(this.falseUrl),
357
                                createTypeReferenceNode('const', undefined))),
358
                      ],
359
                      true),
360
                  )],
361
              NodeFlags.Const))
362
    ];
363
  }
364
}
365

366
export class DataTypeUnion extends Builtin {
1×
367
  constructor(url: string, private readonly wk: Builtin[], doc: string) {
1×
368
    super(url, '', doc);
1×
369
  }
370

371
  toNode(): DeclarationStatement[] {
372
    return [withComments(
11×
373
        this.doc,
374
        createTypeAliasDeclaration(
375
            /*decorators=*/[],
376
            createModifiersFromModifierFlags(ModifierFlags.Export),
377
            this.subject.name, /*typeParameters=*/[],
378
            createUnionTypeNode(this.wk.map(
379
                wk => createTypeReferenceNode(
66×
380
                    wk.subject.name, /*typeArguments=*/[])))))];
381
  }
382
}
383

384
/**
385
 * Defines a Sort order between Class declarations.
386
 *
387
 * DataTypes come first, by the 'DataType' union itself, followed by all regular
388
 * classes. Within each group, class names are ordered alphabetically in UTF-16
389
 * code units order.
390
 */
391
export function Sort(a: Class, b: Class): number {
1×
392
  if (a instanceof Builtin && !(a instanceof DataTypeUnion)) {
241×
393
    if (b instanceof Builtin && !(b instanceof DataTypeUnion)) {
Branches [[28, 1]] missed. 121×
394
      return a.subject.name.localeCompare(b.subject.name);
121×
395
    } else {
396
      return -1;
!
397
    }
398
  } else if (b instanceof Builtin && !(b instanceof DataTypeUnion)) {
120×
399
    return +1;
59×
400
  } else if (a instanceof DataTypeUnion) {
Branches [[32, 0]] missed. 61×
401
    return b instanceof DataTypeUnion ? 0 : -1;
Branches [[33, 0], [33, 1]] missed. !
402
  } else if (b instanceof DataTypeUnion) {
61×
403
    return a instanceof DataTypeUnion ? 0 : +1;
Branches [[35, 0]] missed. 23×
404
  } else {
405
    return CompareKeys(a.subject, b.subject);
38×
406
  }
407
}
408

409
function CompareKeys(a: TSubject, b: TSubject): number {
410
  const byName = a.name.localeCompare(b.name);
85×
411
  if (byName !== 0) return byName;
Branches [[36, 1]] missed. 85×
412

413
  return a.href.localeCompare(b.href);
!
414
}
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2021 Coveralls, Inc