From c5ec461c16adfca1450236093604e27d3ac9001d Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Fri, 26 Jan 2018 16:52:40 -0800 Subject: [PATCH 1/3] Implement strict initialization. --- package-lock.json | 6 +++--- package.json | 2 +- src/analysis-format/generate-analysis.ts | 2 +- src/core/analysis-context.ts | 14 ++++++------- src/core/utils.ts | 4 ++-- src/css/css-document.ts | 8 ++------ src/css/css-parser.ts | 3 ++- src/html/html-document.ts | 6 +----- src/html/html-parser.ts | 20 +++++++++++-------- src/html/html-script-tag.ts | 4 ++-- src/javascript/javascript-document.ts | 4 ++-- src/javascript/javascript-parser.ts | 10 ++++++---- src/json/json-document.ts | 8 ++------ src/json/json-parser.ts | 3 ++- src/model/document.ts | 4 ++-- src/model/element-base.ts | 12 +++++------ src/model/element-mixin.ts | 1 + src/model/element.ts | 3 +-- src/model/inline-document.ts | 6 ++++-- src/model/reference.ts | 1 + src/parser/document.ts | 8 ++++---- src/parser/parser.ts | 2 +- src/polymer/behavior-scanner.ts | 3 ++- src/polymer/behavior.ts | 5 +---- src/polymer/polymer-element-mixin.ts | 5 +++-- src/polymer/polymer-element.ts | 5 ++++- src/test/parser/document_test.ts | 6 +++--- src/test/polymer/behavior-scanner_test.ts | 3 +++ .../typescript/typescript-analyzer_test.ts | 4 ++-- src/test/url-loader/multi-url-loader_test.ts | 10 ++-------- .../url-loader/prefixed-url-loader_test.ts | 9 ++------- src/typescript/typescript-document.ts | 10 ++-------- src/typescript/typescript-preparser.ts | 3 ++- tsconfig.json | 1 + 34 files changed, 92 insertions(+), 103 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d5825f9..092d179b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4686,9 +4686,9 @@ "dev": true }, "typescript": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", - "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", + "version": "2.8.0-dev.20180125", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.0-dev.20180125.tgz", + "integrity": "sha512-D5kzJ/iG752X4Nko+Ga3B9Iu/96qj2MIiqAhDIVuzMIdVp1Hi7MHrDqzaU/FjLCiiq2OuhBdIRqkk3Vr5a57HA==", "dev": true }, "typescript-json-schema": { diff --git a/package.json b/package.json index 41160396..61821820 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "temp": "^0.8.3", "tsc-then": "^1.0.1", "tslint": "^4.1.1", - "typescript": "^2.6.1", + "typescript": "^2.8.0-dev.20180125", "typescript-json-schema": "^0.19.0" }, "dependencies": { diff --git a/src/analysis-format/generate-analysis.ts b/src/analysis-format/generate-analysis.ts index ad5ff303..21cba565 100644 --- a/src/analysis-format/generate-analysis.ts +++ b/src/analysis-format/generate-analysis.ts @@ -306,7 +306,7 @@ function serializeElementMixin( function serializePolymerBehaviorAsElementMixin( behavior: ResolvedPolymerBehavior, urlResolver: UrlResolver): ElementMixin { const metadata = serializeElementLike(behavior, urlResolver) as ElementMixin; - metadata.name = behavior.className; + metadata.name = behavior.className!; metadata.privacy = behavior.privacy; if (behavior.mixins.length > 0) { metadata.mixins = behavior.mixins.map((m) => m.identifier); diff --git a/src/core/analysis-context.ts b/src/core/analysis-context.ts index 2f814dc9..f1103dfa 100644 --- a/src/core/analysis-context.ts +++ b/src/core/analysis-context.ts @@ -137,6 +137,7 @@ export class AnalysisContext { options.scanners || AnalysisContext.getDefaultScanners(this._lazyEdges); this._cache = cache || new AnalysisCache(); this._generation = generation || 0; + this._analysisComplete = Promise.resolve(); } /** @@ -431,12 +432,11 @@ export class AnalysisContext { }; try { const parsedDoc = this._parseContents( - feature.type, - feature.contents, - containingDocument.url, - {locationOffset, astNode: feature.astNode}); - // Inline documents inherit the base url of their containers. - parsedDoc.baseUrl = containingDocument.document.baseUrl; + feature.type, feature.contents, containingDocument.url, { + locationOffset, + astNode: feature.astNode, + baseUrl: containingDocument.document.baseUrl + }); const scannedDoc = await this._scanDocument( parsedDoc, feature.attachedComment, containingDocument.document); @@ -494,7 +494,7 @@ export class AnalysisContext { */ private _parseContents( type: string, contents: string, url: ResolvedUrl, - inlineInfo?: InlineDocInfo): ParsedDocument { + inlineInfo?: InlineDocInfo): ParsedDocument { const parser = this.parsers.get(type); if (parser == null) { throw new NoKnownParserError(`No parser for for file type ${type}`); diff --git a/src/core/utils.ts b/src/core/utils.ts index 8e28dcf5..029b8561 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -46,8 +46,8 @@ export function isPathInside(directory: string, filePath: string): boolean { export class Deferred { promise: Promise; - resolve: (result: T) => void; - reject: (error: Error) => void; + resolve!: (result: T) => void; + reject!: (error: Error) => void; resolved = false; rejected = false; error: any; diff --git a/src/css/css-document.ts b/src/css/css-document.ts index 4523e806..68aef6f0 100644 --- a/src/css/css-document.ts +++ b/src/css/css-document.ts @@ -15,18 +15,14 @@ import * as shady from 'shady-css-parser'; import {SourceRange} from '../model/model'; -import {Options, ParsedDocument, StringifyOptions} from '../parser/document'; +import {ParsedDocument, StringifyOptions} from '../parser/document'; import cssbeautify = require('cssbeautify'); export interface Visitor { visit(node: shady.Node): void; } export class ParsedCssDocument extends ParsedDocument { - type = 'css'; - - constructor(from: Options) { - super(from); - } + readonly type = 'css'; visit(visitors: Visitor[]) { for (const node of this) { diff --git a/src/css/css-parser.ts b/src/css/css-parser.ts index af86919d..9c909e03 100644 --- a/src/css/css-parser.ts +++ b/src/css/css-parser.ts @@ -30,12 +30,13 @@ export class CssParser implements Parser { parse( contents: string, url: ResolvedUrl, _urlResolver: UrlResolver, - inlineInfo?: InlineDocInfo): ParsedCssDocument { + inlineInfo?: InlineDocInfo): ParsedCssDocument { const ast = this._parser.parse(contents); const isInline = !!inlineInfo; inlineInfo = inlineInfo || {}; return new ParsedCssDocument({ url, + baseUrl: inlineInfo.baseUrl, contents, ast, locationOffset: inlineInfo.locationOffset, diff --git a/src/html/html-document.ts b/src/html/html-document.ts index d5bef4d7..c322bf4a 100644 --- a/src/html/html-document.ts +++ b/src/html/html-document.ts @@ -18,7 +18,7 @@ import * as parse5 from 'parse5'; import {ASTNode} from 'parse5'; import {SourceRange} from '../model/model'; -import {Options, ParsedDocument, StringifyOptions} from '../parser/document'; +import {ParsedDocument, StringifyOptions} from '../parser/document'; /** * The ASTs of the HTML elements needed to represent Polymer elements. @@ -29,10 +29,6 @@ export interface HtmlVisitor { (node: ASTNode): void; } export class ParsedHtmlDocument extends ParsedDocument { type = 'html'; - constructor(from: Options) { - super(from); - } - visit(visitors: HtmlVisitor[]) { dom5.nodeWalk(this.ast, (node) => { visitors.forEach((visitor) => visitor(node)); diff --git a/src/html/html-parser.ts b/src/html/html-parser.ts index c3b00aa9..c49372e8 100644 --- a/src/html/html-parser.ts +++ b/src/html/html-parser.ts @@ -31,7 +31,7 @@ export class HtmlParser implements Parser { */ parse( contents: string, url: ResolvedUrl, urlResolver: UrlResolver, - inlineInfo?: InlineDocInfo): ParsedHtmlDocument { + inlineInfo?: InlineDocInfo): ParsedHtmlDocument { const ast = parseHtml(contents, {locationInfo: true}); // There should be at most one tag and it must be inside tag. @@ -42,16 +42,20 @@ export class HtmlParser implements Parser { p.hasTagName('base'), p.hasAttr('href'))); - let baseUrl; + const isInline = !!inlineInfo; + inlineInfo = inlineInfo || {}; + + let baseUrl: ResolvedUrl = + inlineInfo.baseUrl !== undefined ? inlineInfo.baseUrl : url; if (baseTag) { - const baseHref = getAttribute(baseTag, 'href')! as FileRelativeUrl; - baseUrl = urlResolver.resolve(url, baseHref, undefined); - } else { - baseUrl = url; + const baseTagHref = getAttribute(baseTag, 'href')! as FileRelativeUrl; + const resolvedBaseTagHref = + urlResolver.resolve(url, baseTagHref, undefined); + if (resolvedBaseTagHref !== undefined) { + baseUrl = resolvedBaseTagHref; + } } - const isInline = !!inlineInfo; - inlineInfo = inlineInfo || {}; return new ParsedHtmlDocument({ url, baseUrl, diff --git a/src/html/html-script-tag.ts b/src/html/html-script-tag.ts index aa3001eb..6a2f0d4a 100644 --- a/src/html/html-script-tag.ts +++ b/src/html/html-script-tag.ts @@ -21,14 +21,14 @@ import {FileRelativeUrl} from '../model/url'; * represents a script tag with a `src` attribute as an import, so that the * analyzer loads and parses the referenced document. */ -export class ScriptTagImport extends Import { type: 'html-script'; } +export class ScriptTagImport extends Import { readonly type = 'html-script'; } /** * A synthetic import that provides the document containing the script tag to * the javascript document defined/referenced by the script tag. */ export class ScriptTagBackReferenceImport extends Import { - type: 'html-script-back-reference'; + readonly type = 'html-script-back-reference'; } export class ScannedScriptTagImport extends ScannedImport { diff --git a/src/javascript/javascript-document.ts b/src/javascript/javascript-document.ts index 9ff7fdef..aae371af 100644 --- a/src/javascript/javascript-document.ts +++ b/src/javascript/javascript-document.ts @@ -43,9 +43,9 @@ export interface Options extends ParsedDocumentOptions { } export class JavaScriptDocument extends ParsedDocument { - type = 'js'; + readonly type = 'js'; private visitorSkips = new Map(); - ast: Program; + ast!: Program; // assigned in super, this type is just a refinement. /** * How the js document was parsed. If 'module' then the source code is diff --git a/src/javascript/javascript-parser.ts b/src/javascript/javascript-parser.ts index 049df39c..9a52236a 100644 --- a/src/javascript/javascript-parser.ts +++ b/src/javascript/javascript-parser.ts @@ -44,11 +44,11 @@ const baseParseOptions: babylon.BabylonOptions = { (baseParseOptions as any)['ranges'] = true; export class JavaScriptParser implements Parser { - sourceType: SourceType; + readonly sourceType?: SourceType; parse( contents: string, url: ResolvedUrl, _urlResolver: UrlResolver, - inlineInfo?: InlineDocInfo): JavaScriptDocument { + inlineInfo?: InlineDocInfo): JavaScriptDocument { const isInline = !!inlineInfo; inlineInfo = inlineInfo || {}; const result = parseJs( @@ -58,6 +58,7 @@ export class JavaScriptParser implements Parser { const minimalDocument = new JavaScriptDocument({ url, contents, + baseUrl: inlineInfo.baseUrl, ast: null as any, locationOffset: inlineInfo.locationOffset, astNode: inlineInfo.astNode, @@ -71,6 +72,7 @@ export class JavaScriptParser implements Parser { return new JavaScriptDocument({ url, contents, + baseUrl: inlineInfo.baseUrl, ast: result.program, locationOffset: inlineInfo.locationOffset, astNode: inlineInfo.astNode, @@ -81,11 +83,11 @@ export class JavaScriptParser implements Parser { } export class JavaScriptModuleParser extends JavaScriptParser { - sourceType: SourceType = 'module'; + readonly sourceType: SourceType = 'module'; } export class JavaScriptScriptParser extends JavaScriptParser { - sourceType: SourceType = 'script'; + readonly sourceType: SourceType = 'script'; } export type ParseResult = { diff --git a/src/json/json-document.ts b/src/json/json-document.ts index a6a64d9b..1a10fc02 100644 --- a/src/json/json-document.ts +++ b/src/json/json-document.ts @@ -13,7 +13,7 @@ */ import {SourceRange} from '../model/model'; -import {Options, ParsedDocument} from '../parser/document'; +import {ParsedDocument} from '../parser/document'; export type Json = JsonObject|JsonArray|number|string|boolean|null; export interface JsonObject { [key: string]: Json; } @@ -22,11 +22,7 @@ export interface JsonArray extends Array {} export interface Visitor { visit(node: Json): void; } export class ParsedJsonDocument extends ParsedDocument { - type = 'json'; - - constructor(from: Options) { - super(from); - } + readonly type = 'json'; visit(visitors: Visitor[]) { this._visit(this.ast, visitors); diff --git a/src/json/json-parser.ts b/src/json/json-parser.ts index 5cdc3b0c..4c95af84 100644 --- a/src/json/json-parser.ts +++ b/src/json/json-parser.ts @@ -22,11 +22,12 @@ import {ParsedJsonDocument} from './json-document'; export class JsonParser implements Parser { parse( contents: string, url: ResolvedUrl, _urlResolver: UrlResolver, - inlineDocInfo: InlineDocInfo): ParsedJsonDocument { + inlineDocInfo: InlineDocInfo): ParsedJsonDocument { const isInline = !!inlineDocInfo; inlineDocInfo = inlineDocInfo || {}; return new ParsedJsonDocument({ url, + baseUrl: inlineDocInfo.baseUrl, contents, ast: JSON.parse(contents), locationOffset: inlineDocInfo.locationOffset, diff --git a/src/model/document.ts b/src/model/document.ts index ce0d4f6b..7758fa95 100644 --- a/src/model/document.ts +++ b/src/model/document.ts @@ -43,7 +43,7 @@ export class ScannedDocument { get sourceRange() { return this.document.sourceRange; } - get astNode(): AstNodeWithLanguage|null { + get astNode(): AstNodeWithLanguage|undefined { return this.document.astNode; } @@ -157,7 +157,7 @@ export class Document return this._scannedDocument.sourceRange; } - get astNode(): AstNodeWithLanguage|null { + get astNode(): AstNodeWithLanguage|undefined { return this._scannedDocument.astNode; } diff --git a/src/model/element-base.ts b/src/model/element-base.ts index 4bd4992c..d1ef4370 100644 --- a/src/model/element-base.ts +++ b/src/model/element-base.ts @@ -40,14 +40,14 @@ export abstract class ScannedElementBase implements Resolvable { demos: Demo[] = []; events: Map = new Map(); sourceRange: SourceRange|undefined; - staticMethods: Map; - methods: Map; - astNode: babel.Node|null; + staticMethods: Map = new Map(); + methods: Map = new Map(); + astNode: babel.Node|null = null; warnings: Warning[] = []; jsdoc?: jsdoc.Annotation; 'slots': Slot[] = []; mixins: ScannedReference[] = []; - privacy: Privacy; + privacy!: Privacy; abstract: boolean = false; superClass?: ScannedReference = undefined; @@ -145,8 +145,8 @@ export abstract class ElementBase extends Class implements Feature { // Initialization of these attributes is kinda awkward, as they're part // of the inheritance system. See `inheritFrom` below which *may* be // called by our superclass, but may not be. - this.attributes = this.attributes || new Map(); - this.events = this.events || new Map(); + this.attributes = (this as any).attributes || new Map(); + this.events = (this as any).events || new Map(); if (attributes !== undefined) { this._overwriteInherited(this.attributes, attributes, undefined, true); diff --git a/src/model/element-mixin.ts b/src/model/element-mixin.ts index effcc19a..14f4df43 100644 --- a/src/model/element-mixin.ts +++ b/src/model/element-mixin.ts @@ -43,5 +43,6 @@ export class ElementMixin extends ElementBase implements Feature { constructor(init: ElementMixinInit, document: Document) { super(init, document); this.kinds.add('element-mixin'); + this.name = init.name; } } diff --git a/src/model/element.ts b/src/model/element.ts index fe9e11f4..34644f7c 100644 --- a/src/model/element.ts +++ b/src/model/element.ts @@ -14,7 +14,7 @@ import {Document} from './document'; import {ElementBase, ElementBaseInit, ScannedElementBase} from './element-base'; -import {Feature, Privacy} from './feature'; +import {Feature} from './feature'; import {ScannedReference} from './reference'; export {Visitor} from '../javascript/estree-visitor'; @@ -26,7 +26,6 @@ export class ScannedElement extends ScannedElementBase { return this.className; } superClass?: ScannedReference; - privacy: Privacy; /** * For customized built-in elements, the tagname of the superClass. diff --git a/src/model/inline-document.ts b/src/model/inline-document.ts index 1f40e2eb..280c3663 100644 --- a/src/model/inline-document.ts +++ b/src/model/inline-document.ts @@ -26,11 +26,13 @@ import {ScannedFeature} from './feature'; import {unsafeAsMutable} from './immutable'; import {Resolvable} from './resolvable'; import {LocationOffset, SourceRange} from './source-range'; +import {ResolvedUrl} from './url'; import {Warning} from './warning'; -export interface InlineDocInfo { - astNode?: AstNode; +export interface InlineDocInfo { + astNode?: AstNodeWithLanguage; locationOffset?: LocationOffset; + baseUrl?: ResolvedUrl; } export type AstNodeWithLanguage = { diff --git a/src/model/reference.ts b/src/model/reference.ts index 810ed9fa..7d6b4bda 100644 --- a/src/model/reference.ts +++ b/src/model/reference.ts @@ -32,6 +32,7 @@ export class ScannedReference extends ScannedFeature implements Resolvable { identifier: string, sourceRange: SourceRange, astNode?: any, description?: string, jsdoc?: Annotation, warnings?: Warning[]) { super(sourceRange, astNode, description, jsdoc, warnings); + this.sourceRange = sourceRange; this.identifier = identifier; } diff --git a/src/parser/document.ts b/src/parser/document.ts index 52f4f3f2..bf78a6ce 100644 --- a/src/parser/document.ts +++ b/src/parser/document.ts @@ -34,7 +34,7 @@ export abstract class ParsedDocument { * If not null, this is an inline document, and astNode is the AST Node of * this document inside of the parent. (e.g. the