From c5c6d84fe6bca821dee283cedd691a620a79e4e4 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 10 Jan 2018 10:41:15 +0000 Subject: [PATCH] style(aio): enforce strict TypeScript checks (#21342) Closes #20646 PR Close #21342 --- .../package.json | 1 - aio/e2e/api.po.ts | 2 +- aio/e2e/app.po.ts | 2 +- aio/src/app/app.component.spec.ts | 24 +++++++------- aio/src/app/app.component.ts | 16 +++++----- aio/src/app/app.module.spec.ts | 4 +-- aio/src/app/documents/document-contents.ts | 2 +- .../app/documents/document.service.spec.ts | 16 +++++----- aio/src/app/documents/document.service.ts | 2 +- .../embed-components.service.spec.ts | 4 +-- .../embed-components.service.ts | 4 +-- .../embedded/api/api-list.component.spec.ts | 6 ++-- aio/src/app/embedded/api/api.service.spec.ts | 4 +-- .../embedded/code/code-example.component.ts | 2 +- .../app/embedded/code/code-tabs.component.ts | 6 ++-- .../app/embedded/code/code.component.spec.ts | 4 +-- aio/src/app/embedded/code/code.component.ts | 2 +- .../embedded/code/pretty-printer.service.ts | 8 +++-- .../contributor/contributor-list.component.ts | 2 +- .../contributor/contributor.component.ts | 2 +- .../contributor/contributor.service.spec.ts | 2 +- .../contributor/contributor.service.ts | 2 +- .../live-example.component.spec.ts | 4 +-- .../live-example/live-example.component.ts | 4 +-- .../resource/resource.service.spec.ts | 2 +- .../doc-viewer/doc-viewer.component.spec.ts | 6 ++-- .../layout/doc-viewer/doc-viewer.component.ts | 19 +++++++----- aio/src/app/layout/toc/toc.component.spec.ts | 14 ++++----- aio/src/app/layout/toc/toc.component.ts | 2 +- .../app/navigation/navigation.service.spec.ts | 18 +++++------ aio/src/app/navigation/navigation.service.ts | 7 +++-- aio/src/app/search/search.service.spec.ts | 2 +- aio/src/app/shared/attribute-utils.spec.ts | 23 ++++++-------- aio/src/app/shared/attribute-utils.ts | 6 ++-- aio/src/app/shared/copier.service.ts | 2 +- .../app/shared/custom-icon-registry.spec.ts | 6 ++-- aio/src/app/shared/custom-icon-registry.ts | 2 +- aio/src/app/shared/ga.service.ts | 4 +-- aio/src/app/shared/location.service.spec.ts | 31 ++++++++----------- aio/src/app/shared/location.service.ts | 15 +++++---- aio/src/app/shared/logger.service.ts | 6 ++-- aio/src/app/shared/scroll-spy.service.spec.ts | 11 ++++--- aio/src/app/shared/scroll-spy.service.ts | 3 +- aio/src/app/shared/scroll.service.spec.ts | 4 +-- aio/src/app/shared/scroll.service.ts | 4 +-- .../search-results.component.spec.ts | 10 +++--- .../search-results.component.ts | 2 +- .../shared/select/select.component.spec.ts | 16 +++++----- aio/src/app/shared/toc.service.spec.ts | 16 +++++----- aio/src/app/shared/toc.service.ts | 8 +++-- .../app/sw-updates/sw-updates.service.spec.ts | 6 ++-- aio/src/app/sw-updates/sw-updates.service.ts | 2 +- aio/src/testing/doc-viewer-utils.ts | 4 +-- aio/src/testing/embed-components-utils.ts | 2 +- aio/src/testing/location.service.ts | 4 +-- aio/src/testing/logger.service.ts | 8 ++--- aio/tsconfig.json | 2 ++ 57 files changed, 198 insertions(+), 194 deletions(-) diff --git a/aio/content/examples/service-worker-getting-started/package.json b/aio/content/examples/service-worker-getting-started/package.json index 6886be13b7..41bbb4a01f 100644 --- a/aio/content/examples/service-worker-getting-started/package.json +++ b/aio/content/examples/service-worker-getting-started/package.json @@ -18,7 +18,6 @@ "@angular/core": "^5.0.0", "@angular/forms": "^5.0.0", "@angular/http": "^5.0.0", - "@angular/service-worker": "^5.0.0", "@angular/platform-browser": "^5.0.0", "@angular/platform-browser-dynamic": "^5.0.0", "@angular/router": "^5.0.0", diff --git a/aio/e2e/api.po.ts b/aio/e2e/api.po.ts index dab4538b82..1b632f698d 100644 --- a/aio/e2e/api.po.ts +++ b/aio/e2e/api.po.ts @@ -25,7 +25,7 @@ export class ApiPage extends SitePage { // and we want to be able to pull out the code elements from only the first level // if `onlyDirect` is set to `true`. const selector = `.descendants.${docType} ${onlyDirect ? '>' : ''} li > :not(ul) code`; - return element.all(by.css(selector)).map(item => item.getText()); + return element.all(by.css(selector)).map(item => item && item.getText()); } getOverview(docType) { diff --git a/aio/e2e/app.po.ts b/aio/e2e/app.po.ts index dde2ffd955..fb2050e4d6 100644 --- a/aio/e2e/app.po.ts +++ b/aio/e2e/app.po.ts @@ -62,6 +62,6 @@ export class SitePage { getSearchResults() { const results = element.all(by.css('.search-results li')); browser.wait(ExpectedConditions.presenceOf(results.first()), 8000); - return results.map(link => link.getText()); + return results.map(link => link && link.getText()); } } diff --git a/aio/src/app/app.component.spec.ts b/aio/src/app/app.component.spec.ts index 1be070b3f3..4450ed12c7 100644 --- a/aio/src/app/app.component.spec.ts +++ b/aio/src/app/app.component.spec.ts @@ -314,7 +314,7 @@ describe('AppComponent', () => { it('should not navigate when change to a version without a url', () => { setupSelectorForTesting(); const versionWithoutUrlIndex = component.docVersions.length; - const versionWithoutUrl = component.docVersions[versionWithoutUrlIndex] = { title: 'foo', url: null }; + const versionWithoutUrl = component.docVersions[versionWithoutUrlIndex] = { title: 'foo' }; selectElement.triggerEventHandler('change', { option: versionWithoutUrl, index: versionWithoutUrlIndex }); expect(locationService.go).not.toHaveBeenCalled(); }); @@ -520,9 +520,9 @@ describe('AppComponent', () => { describe('aio-toc', () => { let tocDebugElement: DebugElement; - let tocContainer: DebugElement; + let tocContainer: DebugElement|null; - const setHasFloatingToc = hasFloatingToc => { + const setHasFloatingToc = (hasFloatingToc: boolean) => { component.hasFloatingToc = hasFloatingToc; fixture.detectChanges(); @@ -551,12 +551,12 @@ describe('AppComponent', () => { }); it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => { - expect(tocContainer.styles['max-height']).toBeNull(); + expect(tocContainer!.styles['max-height']).toBeNull(); component.tocMaxHeight = '100'; fixture.detectChanges(); - expect(tocContainer.styles['max-height']).toBe('100px'); + expect(tocContainer!.styles['max-height']).toBe('100px'); }); it('should restrain scrolling inside the ToC container', () => { @@ -565,7 +565,7 @@ describe('AppComponent', () => { expect(restrainScrolling).not.toHaveBeenCalled(); - tocContainer.triggerEventHandler('mousewheel', evt); + tocContainer!.triggerEventHandler('mousewheel', evt); expect(restrainScrolling).toHaveBeenCalledWith(evt); }); }); @@ -591,7 +591,7 @@ describe('AppComponent', () => { initializeTest(); fixture.detectChanges(); const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement; - expect(banner.textContent.trim()).toEqual(''); + expect(banner.textContent!.trim()).toEqual(''); }); }); @@ -985,9 +985,9 @@ describe('AppComponent', () => { checkHostClass('mode', 'archive'); }); - function checkHostClass(type, value) { + function checkHostClass(type: string, value: string) { const host = fixture.debugElement; - const classes = host.properties['className']; + const classes: string = host.properties['className']; const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0); expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`); expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`); @@ -1212,10 +1212,10 @@ class TestHttpClient { if (/navigation\.json/.test(url)) { data = this.navJson; } else { - const match = /generated\/docs\/(.+)\.json/.exec(url); - const id = match[1]; + const match = /generated\/docs\/(.+)\.json/.exec(url)!; + const id = match[1]!; // Make up a title for test purposes - const title = id.split('/').pop().replace(/^([a-z])/, (_, letter) => letter.toUpperCase()); + const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase()); const h1 = (id === 'no-title') ? '' : `

${title}

`; const contents = `${h1}

Some heading

`; data = { id, contents }; diff --git a/aio/src/app/app.component.ts b/aio/src/app/app.component.ts index d35f9b57b5..2cb788df2f 100644 --- a/aio/src/app/app.component.ts +++ b/aio/src/app/app.component.ts @@ -152,19 +152,19 @@ export class AppComponent implements OnInit { this.navigationService.navigationViews.map(views => views['docVersions'])) .subscribe(([versionInfo, versions]) => { // TODO(pbd): consider whether we can lookup the stable and next versions from the internet - const computedVersions = [ + const computedVersions: NavigationNode[] = [ { title: 'next', url: 'https://next.angular.io' }, { title: 'stable', url: 'https://angular.io' }, ]; if (this.deployment.mode === 'archive') { - computedVersions.push({ title: `v${versionInfo.major}`, url: null }); + computedVersions.push({ title: `v${versionInfo.major}` }); } this.docVersions = [...computedVersions, ...versions]; // Find the current version - eithers title matches the current deployment mode // or its title matches the major version of the current version info this.currentDocVersion = this.docVersions.find(version => - version.title === this.deployment.mode || version.title === `v${versionInfo.major}`); + version.title === this.deployment.mode || version.title === `v${versionInfo.major}`)!; this.currentDocVersion.title += ` (v${versionInfo.raw})`; }); @@ -232,7 +232,7 @@ export class AppComponent implements OnInit { } @HostListener('window:resize', ['$event.target.innerWidth']) - onResize(width) { + onResize(width: number) { this.isSideBySide = width > this.sideBySideWidth; this.showFloatingToc.next(width > this.showFloatingTocWidth); } @@ -252,7 +252,7 @@ export class AppComponent implements OnInit { } // Deal with anchor clicks; climb DOM tree until anchor found (or null) - let target = eventTarget; + let target: HTMLElement|null = eventTarget; while (target && !(target instanceof HTMLAnchorElement)) { target = target.parentElement; } @@ -335,8 +335,8 @@ export class AppComponent implements OnInit { // Must wait until now for mat-toolbar to be measurable. const el = this.hostElement.nativeElement as Element; this.tocMaxHeightOffset = - el.querySelector('footer').clientHeight + - el.querySelector('.app-toolbar').clientHeight + + el.querySelector('footer')!.clientHeight + + el.querySelector('.app-toolbar')!.clientHeight + 24; // fudge margin } @@ -375,7 +375,7 @@ export class AppComponent implements OnInit { } } - doSearch(query) { + doSearch(query: string) { this.searchResults = this.searchService.search(query); this.showSearchResults = !!query; } diff --git a/aio/src/app/app.module.spec.ts b/aio/src/app/app.module.spec.ts index 7568db0323..26269d6f36 100644 --- a/aio/src/app/app.module.spec.ts +++ b/aio/src/app/app.module.spec.ts @@ -23,7 +23,7 @@ describe('AppModule', () => { }); it('should provide a list of eagerly-loaded embedded components', () => { - const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector])); + const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector]))!; const selectorCount = eagerSelector.split(',').length; expect(eagerSelector).not.toBeNull(); @@ -34,7 +34,7 @@ describe('AppModule', () => { }); it('should provide a list of lazy-loaded embedded components', () => { - const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example')); + const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example'))!; const selectorCount = lazySelector.split(',').length; expect(lazySelector).not.toBeNull(); diff --git a/aio/src/app/documents/document-contents.ts b/aio/src/app/documents/document-contents.ts index a625759f0f..ca0e398db4 100644 --- a/aio/src/app/documents/document-contents.ts +++ b/aio/src/app/documents/document-contents.ts @@ -2,5 +2,5 @@ export interface DocumentContents { /** The unique identifier for this document */ id: string; /** The HTML to display in the doc viewer */ - contents: string; + contents: string|null; } diff --git a/aio/src/app/documents/document.service.spec.ts b/aio/src/app/documents/document.service.spec.ts index 1a41282a75..504e120075 100644 --- a/aio/src/app/documents/document.service.spec.ts +++ b/aio/src/app/documents/document.service.spec.ts @@ -50,7 +50,7 @@ describe('DocumentService', () => { }); it('should emit a document each time the location changes', () => { - let latestDocument: DocumentContents; + let latestDocument: DocumentContents|undefined; const doc0 = { contents: 'doc 0', id: 'initial/doc' }; const doc1 = { contents: 'doc 1', id: 'new/doc' }; const { docService, locationService } = getServices('initial/doc'); @@ -67,7 +67,7 @@ describe('DocumentService', () => { }); it('should emit the not-found document if the document is not found on the server', () => { - let currentDocument: DocumentContents; + let currentDocument: DocumentContents|undefined; const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '

Page Not Found

' }; const { docService } = getServices('missing/doc'); docService.currentDocument.subscribe(doc => currentDocument = doc); @@ -82,7 +82,7 @@ describe('DocumentService', () => { }); it('should emit a hard-coded not-found document if the not-found document is not found on the server', () => { - let currentDocument: DocumentContents; + let currentDocument: DocumentContents|undefined; const hardCodedNotFoundDoc = { contents: 'Document not found', id: FILE_NOT_FOUND_ID }; const nextDoc = { contents: 'Next Doc', id: 'new/doc' }; const { docService, locationService } = getServices(FILE_NOT_FOUND_ID); @@ -99,7 +99,7 @@ describe('DocumentService', () => { }); it('should use a hard-coded error doc if the request fails (but not cache it)', () => { - let latestDocument: DocumentContents; + let latestDocument: DocumentContents|undefined; const doc1 = { contents: 'doc 1' }; const doc2 = { contents: 'doc 2' }; const { docService, locationService } = getServices('initial/doc'); @@ -107,7 +107,7 @@ describe('DocumentService', () => { docService.currentDocument.subscribe(doc => latestDocument = doc); httpMock.expectOne({}).flush(null, {status: 500, statusText: 'Server Error'}); - expect(latestDocument.id).toEqual(FETCHING_ERROR_ID); + expect(latestDocument!.id).toEqual(FETCHING_ERROR_ID); locationService.go('new/doc'); httpMock.expectOne({}).flush(doc1); @@ -119,14 +119,14 @@ describe('DocumentService', () => { }); it('should not crash the app if the response is invalid JSON', () => { - let latestDocument: DocumentContents; + let latestDocument: DocumentContents|undefined; const doc1 = { contents: 'doc 1' }; const { docService, locationService } = getServices('initial/doc'); docService.currentDocument.subscribe(doc => latestDocument = doc); httpMock.expectOne({}).flush('this is invalid JSON'); - expect(latestDocument.id).toEqual(FETCHING_ERROR_ID); + expect(latestDocument!.id).toEqual(FETCHING_ERROR_ID); locationService.go('new/doc'); httpMock.expectOne({}).flush(doc1); @@ -134,7 +134,7 @@ describe('DocumentService', () => { }); it('should not make a request to the server if the doc is in the cache already', () => { - let latestDocument: DocumentContents; + let latestDocument: DocumentContents|undefined; let subscription: Subscription; const doc0 = { contents: 'doc 0' }; diff --git a/aio/src/app/documents/document.service.ts b/aio/src/app/documents/document.service.ts index 6ef931127f..84a4170811 100644 --- a/aio/src/app/documents/document.service.ts +++ b/aio/src/app/documents/document.service.ts @@ -52,7 +52,7 @@ export class DocumentService { if ( !this.cache.has(id)) { this.cache.set(id, this.fetchDocument(id)); } - return this.cache.get(id); + return this.cache.get(id)!; } private fetchDocument(id: string): Observable { diff --git a/aio/src/app/embed-components/embed-components.service.spec.ts b/aio/src/app/embed-components/embed-components.service.spec.ts index c4e1d38f08..8a97d56f1b 100644 --- a/aio/src/app/embed-components/embed-components.service.spec.ts +++ b/aio/src/app/embed-components/embed-components.service.spec.ts @@ -127,9 +127,9 @@ describe('EmbedComponentsService', () => { const componentRefs = service.createComponents(host); componentRefs[0].changeDetectorRef.detectChanges(); - const barEl = host.querySelector('aio-eager-bar'); + const barEl = host.querySelector('aio-eager-bar')!; - expect(barEl['aioEagerBarContent']).toBe(projectedContent); + expect((barEl as any)['aioEagerBarContent']).toBe(projectedContent); expect(barEl.innerHTML).toContain(projectedContent); }); diff --git a/aio/src/app/embed-components/embed-components.service.ts b/aio/src/app/embed-components/embed-components.service.ts index 67b9dc78f6..9a9d6403ef 100644 --- a/aio/src/app/embed-components/embed-components.service.ts +++ b/aio/src/app/embed-components/embed-components.service.ts @@ -111,7 +111,7 @@ export class EmbedComponentsService { // Hack: Preserve the current element content, because the factory will empty it out. // Security: The source of this `innerHTML` should always be authored by the documentation // team and is considered to be safe. - host[contentPropertyName] = host.innerHTML; + (host as any)[contentPropertyName] = host.innerHTML; componentRefs.push(factory.create(this.injector, [], host)); } }); @@ -141,7 +141,7 @@ export class EmbedComponentsService { this.componentFactoriesReady.set(compsOrPath, readyPromise); } - return this.componentFactoriesReady.get(compsOrPath); + return this.componentFactoriesReady.get(compsOrPath)!; } /** diff --git a/aio/src/app/embedded/api/api-list.component.spec.ts b/aio/src/app/embedded/api/api-list.component.spec.ts index edb86939f9..157867007a 100644 --- a/aio/src/app/embedded/api/api-list.component.spec.ts +++ b/aio/src/app/embedded/api/api-list.component.spec.ts @@ -34,7 +34,7 @@ describe('ApiListComponent', () => { */ function expectFilteredResult(label: string, itemTest: (item: ApiItem) => boolean) { component.filteredSections.subscribe(filtered => { - let badItem: ApiItem; + let badItem: ApiItem|undefined; expect(filtered.length).toBeGreaterThan(0, 'expected something'); expect(filtered.every(section => section.items.every( item => { @@ -53,7 +53,7 @@ describe('ApiListComponent', () => { }); it('should return all complete sections when no criteria', () => { - let filtered: ApiSection[]; + let filtered: ApiSection[]|undefined; component.filteredSections.subscribe(f => filtered = f); expect(filtered).toEqual(sections); }); @@ -68,7 +68,7 @@ describe('ApiListComponent', () => { component.filteredSections.subscribe(filtered => { expect(filtered.length).toBe(1, 'only one section'); expect(filtered[0].name).toBe('core'); - expect(filtered[0].items.every(item => item.show)).toBe(true, 'all core items shown'); + expect(filtered[0].items.every(item => !!item.show)).toBe(true, 'all core items shown'); }); }); diff --git a/aio/src/app/embedded/api/api.service.spec.ts b/aio/src/app/embedded/api/api.service.spec.ts index 13641a590b..709fd67923 100644 --- a/aio/src/app/embedded/api/api.service.spec.ts +++ b/aio/src/app/embedded/api/api.service.spec.ts @@ -35,8 +35,8 @@ describe('ApiService', () => { let completed = false; service.sections.subscribe( - null, - null, + undefined, + undefined, () => completed = true ); diff --git a/aio/src/app/embedded/code/code-example.component.ts b/aio/src/app/embedded/code/code-example.component.ts index 199051260d..9dae20f4c4 100644 --- a/aio/src/app/embedded/code/code-example.component.ts +++ b/aio/src/app/embedded/code/code-example.component.ts @@ -41,7 +41,7 @@ export class CodeExampleComponent implements OnInit { const element: HTMLElement = this.elementRef.nativeElement; this.language = element.getAttribute('language') || ''; - this.linenums = element.getAttribute('linenums'); + this.linenums = element.getAttribute('linenums') || ''; this.path = element.getAttribute('path') || ''; this.region = element.getAttribute('region') || ''; this.title = element.getAttribute('title') || ''; diff --git a/aio/src/app/embedded/code/code-tabs.component.ts b/aio/src/app/embedded/code/code-tabs.component.ts index 2a726d01fa..03283c5779 100644 --- a/aio/src/app/embedded/code/code-tabs.component.ts +++ b/aio/src/app/embedded/code/code-tabs.component.ts @@ -2,13 +2,13 @@ import { Component, ElementRef, OnInit } from '@angular/core'; export interface TabInfo { - class: string; + class: string|null; code: string; - language: string; + language: string|null; linenums: any; path: string; region: string; - title: string; + title: string|null; } /** diff --git a/aio/src/app/embedded/code/code.component.spec.ts b/aio/src/app/embedded/code/code.component.spec.ts index 1b7de0d68e..91d8ce0db8 100644 --- a/aio/src/app/embedded/code/code.component.spec.ts +++ b/aio/src/app/embedded/code/code.component.spec.ts @@ -36,8 +36,8 @@ describe('CodeComponent', () => { // we take strict measures to wipe it out in the `afterAll` // and make sure THAT runs after the tests by making component creation async afterAll(() => { - delete window['prettyPrint']; - delete window['prettyPrintOne']; + delete (window as any)['prettyPrint']; + delete (window as any)['prettyPrintOne']; }); beforeEach(() => { diff --git a/aio/src/app/embedded/code/code.component.ts b/aio/src/app/embedded/code/code.component.ts index e72b93bbfb..6b2bd850ed 100644 --- a/aio/src/app/embedded/code/code.component.ts +++ b/aio/src/app/embedded/code/code.component.ts @@ -170,7 +170,7 @@ export class CodeComponent implements OnChanges { } } -function leftAlign(text) { +function leftAlign(text: string) { let indent = Number.MAX_VALUE; const lines = text.split('\n'); lines.forEach(line => { diff --git a/aio/src/app/embedded/code/pretty-printer.service.ts b/aio/src/app/embedded/code/pretty-printer.service.ts index 6fdd982908..28131046c9 100644 --- a/aio/src/app/embedded/code/pretty-printer.service.ts +++ b/aio/src/app/embedded/code/pretty-printer.service.ts @@ -7,7 +7,9 @@ import 'rxjs/add/operator/first'; import { Logger } from 'app/shared/logger.service'; -declare const System; +declare const System: { + import(name: string): Promise; +}; type PrettyPrintOne = (code: string, language?: string, linenums?: number | boolean) => string; @@ -24,12 +26,12 @@ export class PrettyPrinter { } private getPrettyPrintOne(): Promise { - const ppo = window['prettyPrintOne']; + const ppo = (window as any)['prettyPrintOne']; return ppo ? Promise.resolve(ppo) : // prettify.js is not in window global; load it with webpack loader System.import('assets/js/prettify.js') .then( - () => window['prettyPrintOne'], + () => (window as any)['prettyPrintOne'], err => { const msg = 'Cannot get prettify.js from server'; this.logger.error(msg, err); diff --git a/aio/src/app/embedded/contributor/contributor-list.component.ts b/aio/src/app/embedded/contributor/contributor-list.component.ts index c61d3454fa..cb31a6ea24 100644 --- a/aio/src/app/embedded/contributor/contributor-list.component.ts +++ b/aio/src/app/embedded/contributor/contributor-list.component.ts @@ -38,7 +38,7 @@ export class ContributorListComponent implements OnInit { }); } - selectGroup(name) { + selectGroup(name: string) { name = name.toLowerCase(); this.selectedGroup = this.groups.find(g => g.name.toLowerCase() === name) || this.groups[0]; this.locationService.setSearch('', {group: this.selectedGroup.name}); diff --git a/aio/src/app/embedded/contributor/contributor.component.ts b/aio/src/app/embedded/contributor/contributor.component.ts index 5b7ab69384..710d797b6c 100644 --- a/aio/src/app/embedded/contributor/contributor.component.ts +++ b/aio/src/app/embedded/contributor/contributor.component.ts @@ -40,7 +40,7 @@ export class ContributorComponent { noPicture = '_no-one.png'; pictureBase = CONTENT_URL_PREFIX + 'images/bios/'; - flipCard(person) { + flipCard(person: Contributor) { person.isFlipped = !person.isFlipped; } } diff --git a/aio/src/app/embedded/contributor/contributor.service.spec.ts b/aio/src/app/embedded/contributor/contributor.service.spec.ts index d44c1cedd4..4808a7f82d 100644 --- a/aio/src/app/embedded/contributor/contributor.service.spec.ts +++ b/aio/src/app/embedded/contributor/contributor.service.spec.ts @@ -43,7 +43,7 @@ describe('ContributorService', () => { it('contributors observable should complete', () => { let completed = false; - contribService.contributors.subscribe(null, null, () => completed = true); + contribService.contributors.subscribe(undefined, undefined, () => completed = true); expect(true).toBe(true, 'observable completed'); }); diff --git a/aio/src/app/embedded/contributor/contributor.service.ts b/aio/src/app/embedded/contributor/contributor.service.ts index 3ae09b353b..21b4a8c8da 100644 --- a/aio/src/app/embedded/contributor/contributor.service.ts +++ b/aio/src/app/embedded/contributor/contributor.service.ts @@ -23,7 +23,7 @@ export class ContributorService { const contributors = this.http.get<{[key: string]: Contributor}>(contributorsPath) // Create group map .map(contribs => { - const contribMap = new Map(); + const contribMap: { [name: string]: Contributor[]} = {}; Object.keys(contribs).forEach(key => { const contributor = contribs[key]; const group = contributor.group; diff --git a/aio/src/app/embedded/live-example/live-example.component.spec.ts b/aio/src/app/embedded/live-example/live-example.component.spec.ts index 10028fccc3..80bd4baff8 100644 --- a/aio/src/app/embedded/live-example/live-example.component.spec.ts +++ b/aio/src/app/embedded/live-example/live-example.component.spec.ts @@ -13,7 +13,7 @@ describe('LiveExampleComponent', () => { let liveExampleComponent: LiveExampleComponent; let fixture: ComponentFixture; let testPath: string; - let liveExampleContent: string; + let liveExampleContent: string|null; //////// test helpers //////// @@ -66,7 +66,7 @@ describe('LiveExampleComponent', () => { .overrideComponent(EmbeddedPlunkerComponent, {set: {template: 'NO IFRAME'}}); testPath = defaultTestPath; - liveExampleContent = undefined; + liveExampleContent = null; }); describe('when not embedded', () => { diff --git a/aio/src/app/embedded/live-example/live-example.component.ts b/aio/src/app/embedded/live-example/live-example.component.ts index b484665697..a0c3289c46 100644 --- a/aio/src/app/embedded/live-example/live-example.component.ts +++ b/aio/src/app/embedded/live-example/live-example.component.ts @@ -94,7 +94,7 @@ export class LiveExampleComponent implements OnInit { let exampleDir = attrs.name; if (!exampleDir) { // take last segment, excluding hash fragment and query params - exampleDir = location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/)[0]; + exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0]; } this.exampleDir = exampleDir.trim(); this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0]; @@ -150,7 +150,7 @@ export class LiveExampleComponent implements OnInit { } @HostListener('window:resize', ['$event.target.innerWidth']) - onResize(width) { + onResize(width: number) { if (this.mode !== 'downloadOnly') { this.calcPlnkrLink(width); } diff --git a/aio/src/app/embedded/resource/resource.service.spec.ts b/aio/src/app/embedded/resource/resource.service.spec.ts index cba759d62a..317115c0b6 100644 --- a/aio/src/app/embedded/resource/resource.service.spec.ts +++ b/aio/src/app/embedded/resource/resource.service.spec.ts @@ -43,7 +43,7 @@ describe('ResourceService', () => { it('categories observable should complete', () => { let completed = false; - resourceService.categories.subscribe(null, null, () => completed = true); + resourceService.categories.subscribe(undefined, undefined, () => completed = true); expect(true).toBe(true, 'observable completed'); }); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts index 688bcab8d2..cbe56f1e1f 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts @@ -43,7 +43,7 @@ describe('DocViewerComponent', () => { describe('#doc', () => { let renderSpy: jasmine.Spy; - const setCurrentDoc = (contents, id = 'fizz/buzz') => { + const setCurrentDoc = (contents: string|null, id = 'fizz/buzz') => { parentComponent.currentDoc = {contents, id}; parentFixture.detectChanges(); }; @@ -432,7 +432,7 @@ describe('DocViewerComponent', () => { }); it('should store the embedded components', async () => { - const embeddedComponents = []; + const embeddedComponents: ComponentRef[] = []; embedIntoSpy.and.returnValue(of(embeddedComponents)); await doRender('Some content'); @@ -678,7 +678,7 @@ describe('DocViewerComponent', () => { describe(`(.${NO_ANIMATIONS}: ${noAnimations})`, () => { beforeEach(() => docViewerEl.classList[noAnimations ? 'add' : 'remove'](NO_ANIMATIONS)); - it('should return an observable', done => { + it('should return an observable', (done: DoneFn) => { docViewer.swapViews().subscribe(done, done.fail); }); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts index a798762c64..f7c9bf99f0 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts @@ -114,12 +114,12 @@ export class DocViewerComponent implements DoCheck, OnDestroy { const hasToc = !!titleEl && !/no-?toc/i.test(titleEl.className); if (hasToc) { - titleEl.insertAdjacentHTML('afterend', ''); + titleEl!.insertAdjacentHTML('afterend', ''); } return () => { this.tocService.reset(); - let title = ''; + let title: string|null = ''; // Only create ToC for docs with an `

` heading. // If you don't want a ToC, add "no-toc" class to `

`. @@ -169,7 +169,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy { * entering animation has been completed. This is useful for work that needs to be done as soon as * the element has been attached to the DOM. */ - protected swapViews(onInsertedCb = () => undefined): Observable { + protected swapViews(onInsertedCb = () => {}): Observable { const raf$ = new Observable(subscriber => { const rafId = requestAnimationFrame(() => { subscriber.next(); @@ -182,15 +182,18 @@ export class DocViewerComponent implements DoCheck, OnDestroy { // According to the [CSSOM spec](https://drafts.csswg.org/cssom/#serializing-css-values), // `time` values should be returned in seconds. const getActualDuration = (elem: HTMLElement) => { - const cssValue = getComputedStyle(elem).transitionDuration; + const cssValue = getComputedStyle(elem).transitionDuration || ''; const seconds = Number(cssValue.replace(/s$/, '')); return 1000 * seconds; }; const animateProp = - (elem: HTMLElement, prop: string, from: string, to: string, duration = 200) => { + (elem: HTMLElement, prop: keyof CSSStyleDeclaration, from: string, to: string, duration = 200) => { const animationsDisabled = !DocViewerComponent.animationsEnabled || this.hostElement.classList.contains(NO_ANIMATIONS); - + if (prop === 'length' || prop === 'parentRule') { + // We cannot animate length or parentRule properties because they are readonly + return this.void$; + } elem.style.transition = ''; return animationsDisabled ? this.void$.do(() => elem.style[prop] = to) @@ -201,7 +204,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy { // setting each style. .switchMap(() => raf$).do(() => elem.style[prop] = from) .switchMap(() => raf$).do(() => elem.style.transition = `all ${duration}ms ease-in-out`) - .switchMap(() => raf$).do(() => elem.style[prop] = to) + .switchMap(() => raf$).do(() => (elem.style as any)[prop] = to) .switchMap(() => timer(getActualDuration(elem))).switchMap(() => this.void$); }; @@ -214,7 +217,7 @@ export class DocViewerComponent implements DoCheck, OnDestroy { done$ = done$ // Remove the current view from the viewer. .switchMap(() => animateLeave(this.currViewContainer)) - .do(() => this.currViewContainer.parentElement.removeChild(this.currViewContainer)) + .do(() => this.currViewContainer.parentElement!.removeChild(this.currViewContainer)) .do(() => this.docRemoved.emit()); } diff --git a/aio/src/app/layout/toc/toc.component.spec.ts b/aio/src/app/layout/toc/toc.component.spec.ts index ed34577bb3..d4285d4452 100644 --- a/aio/src/app/layout/toc/toc.component.spec.ts +++ b/aio/src/app/layout/toc/toc.component.spec.ts @@ -73,18 +73,18 @@ describe('TocComponent', () => { it('should update when the TocItems are updated', () => { tocService.tocList.next([tocItem('Heading A')]); fixture.detectChanges(); - expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); + expect(tocComponentDe.queryAll(By.css('li')).length).toBe(1); tocService.tocList.next([tocItem('Heading A'), tocItem('Heading B'), tocItem('Heading C')]); fixture.detectChanges(); - expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(3); + expect(tocComponentDe.queryAll(By.css('li')).length).toBe(3); }); it('should only display H2 and H3 TocItems', () => { tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C', 'h3')]); fixture.detectChanges(); - const tocItems = tocComponentDe.queryAllNodes(By.css('li')); + const tocItems = tocComponentDe.queryAll(By.css('li')); const textContents = tocItems.map(item => item.nativeNode.textContent.trim()); expect(tocItems.length).toBe(2); @@ -97,12 +97,12 @@ describe('TocComponent', () => { it('should stop listening for TocItems once destroyed', () => { tocService.tocList.next([tocItem('Heading A')]); fixture.detectChanges(); - expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); + expect(tocComponentDe.queryAll(By.css('li')).length).toBe(1); tocComponent.ngOnDestroy(); tocService.tocList.next([tocItem('Heading A', 'h1'), tocItem('Heading B'), tocItem('Heading C')]); fixture.detectChanges(); - expect(tocComponentDe.queryAllNodes(By.css('li')).length).toBe(1); + expect(tocComponentDe.queryAll(By.css('li')).length).toBe(1); }); describe('when fewer than `maxPrimary` TocItems', () => { @@ -339,7 +339,7 @@ describe('TocComponent', () => { it('should re-apply the `active` class when the list elements change', () => { const getActiveTextContent = () => - page.listItems.find(By.css('.active')).nativeElement.textContent.trim(); + page.listItems.find(By.css('.active'))!.nativeElement.textContent.trim(); tocComponent.activeIndex = 1; fixture.detectChanges(); @@ -462,7 +462,7 @@ class TestScrollService { class TestTocService { tocList = new BehaviorSubject(getTestTocList()); activeItemIndex = new BehaviorSubject(null); - setActiveIndex(index) { + setActiveIndex(index: number|null) { this.activeItemIndex.next(index); if (asap.scheduled) { asap.flush(); diff --git a/aio/src/app/layout/toc/toc.component.ts b/aio/src/app/layout/toc/toc.component.ts index 95f4edd14d..4194976f84 100644 --- a/aio/src/app/layout/toc/toc.component.ts +++ b/aio/src/app/layout/toc/toc.component.ts @@ -93,6 +93,6 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy { } } -function count(array: T[], fn: (T) => boolean) { +function count(array: T[], fn: (item: T) => boolean) { return array.reduce((count, item) => fn(item) ? count + 1 : count, 0); } diff --git a/aio/src/app/navigation/navigation.service.spec.ts b/aio/src/app/navigation/navigation.service.spec.ts index 9a4e3dc7cd..4120d43118 100644 --- a/aio/src/app/navigation/navigation.service.spec.ts +++ b/aio/src/app/navigation/navigation.service.spec.ts @@ -45,7 +45,7 @@ describe('NavigationService', () => { it('navigationViews observable should complete', () => { let completed = false; - navService.navigationViews.subscribe(null, null, () => completed = true); + navService.navigationViews.subscribe(undefined, undefined, () => completed = true); expect(true).toBe(true, 'observable completed'); // Stop `$httpMock.verify()` from complaining. @@ -53,15 +53,15 @@ describe('NavigationService', () => { }); it('should return the same object to all subscribers', () => { - let views1: NavigationViews; + let views1: NavigationViews|undefined; navService.navigationViews.subscribe(views => views1 = views); - let views2: NavigationViews; + let views2: NavigationViews|undefined; navService.navigationViews.subscribe(views => views2 = views); httpMock.expectOne({}).flush({ TopBar: [{ url: 'a' }] }); - let views3: NavigationViews; + let views3: NavigationViews|undefined; navService.navigationViews.subscribe(views => views3 = views); expect(views2).toBe(views1); @@ -143,7 +143,7 @@ describe('NavigationService', () => { url: 'b', view: 'SideNav', nodes: [ - sideNavNodes[0].children[0], + sideNavNodes[0].children![0], sideNavNodes[0] ] } @@ -155,8 +155,8 @@ describe('NavigationService', () => { url: 'd', view: 'SideNav', nodes: [ - sideNavNodes[0].children[0].children[1], - sideNavNodes[0].children[0], + sideNavNodes[0].children![0].children![1], + sideNavNodes[0].children![0], sideNavNodes[0] ] } @@ -200,8 +200,8 @@ describe('NavigationService', () => { url: 'c', view: 'SideNav', nodes: [ - sideNavNodes[0].children[0].children[0], - sideNavNodes[0].children[0], + sideNavNodes[0].children![0].children![0], + sideNavNodes[0].children![0], sideNavNodes[0] ] } diff --git a/aio/src/app/navigation/navigation.service.ts b/aio/src/app/navigation/navigation.service.ts index 40dd895c74..a15e6c387d 100644 --- a/aio/src/app/navigation/navigation.service.ts +++ b/aio/src/app/navigation/navigation.service.ts @@ -97,7 +97,7 @@ export class NavigationService { (navMap, url) => { const urlKey = url.startsWith('api/') ? 'api' : url; - return navMap[urlKey] || { '' : { view: '', url: urlKey, nodes: [] }}; + return navMap.get(urlKey) || { '' : { view: '', url: urlKey, nodes: [] }}; }) .publishReplay(1); currentNodes.connect(); @@ -145,7 +145,10 @@ export class NavigationService { if (url) { // Strip off trailing slashes from nodes in the navMap - they are not relevant to matching const cleanedUrl = url.replace(/\/$/, ''); - const navMapItem = navMap[cleanedUrl] = navMap[cleanedUrl] || {}; + if (!navMap.has(cleanedUrl)) { + navMap.set(cleanedUrl, {}); + } + const navMapItem = navMap.get(cleanedUrl)!; navMapItem[view] = { url, view, nodes }; } diff --git a/aio/src/app/search/search.service.spec.ts b/aio/src/app/search/search.service.spec.ts index d8a0bb21ce..edab5ad19a 100644 --- a/aio/src/app/search/search.service.spec.ts +++ b/aio/src/app/search/search.service.spec.ts @@ -56,7 +56,7 @@ describe('SearchService', () => { it('should push the response to the returned observable', () => { const mockSearchResults = { results: ['a', 'b'] }; - let actualSearchResults; + let actualSearchResults: any; (mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults)); service.search('some query').subscribe(results => actualSearchResults = results); expect(actualSearchResults).toEqual(mockSearchResults); diff --git a/aio/src/app/shared/attribute-utils.spec.ts b/aio/src/app/shared/attribute-utils.spec.ts index b8d4c883eb..e40ff28703 100644 --- a/aio/src/app/shared/attribute-utils.spec.ts +++ b/aio/src/app/shared/attribute-utils.spec.ts @@ -8,29 +8,26 @@ describe('Attribute Utilities', () => { beforeEach(() => { const div = document.createElement('div'); div.innerHTML = `
`; - testEl = div.querySelector('div'); + testEl = div.querySelector('div')!; }); describe('getAttrs', () => { - - beforeEach(() => { - this.expectedMap = { - a: '', - b: 'true', - c: 'false', - d: 'foo', - 'd-e': '' - }; - }); + const expectedMap = { + a: '', + b: 'true', + c: 'false', + d: 'foo', + 'd-e': '' + }; it('should get attr map from getAttrs(element)', () => { const actual = getAttrs(testEl); - expect(actual).toEqual(this.expectedMap); + expect(actual).toEqual(expectedMap); }); it('should get attr map from getAttrs(elementRef)', () => { const actual = getAttrs(new ElementRef(testEl)); - expect(actual).toEqual(this.expectedMap); + expect(actual).toEqual(expectedMap); }); }); diff --git a/aio/src/app/shared/attribute-utils.ts b/aio/src/app/shared/attribute-utils.ts index 2eb08baa29..4340c41b6e 100644 --- a/aio/src/app/shared/attribute-utils.ts +++ b/aio/src/app/shared/attribute-utils.ts @@ -10,7 +10,7 @@ interface StringMap { [index: string]: string; } */ export function getAttrs(el: HTMLElement | ElementRef): StringMap { const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes; - const attrMap = {}; + const attrMap: StringMap = {}; for (const attr of attrs as any /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) { attrMap[attr.name.toLowerCase()] = attr.value; } @@ -24,7 +24,7 @@ export function getAttrs(el: HTMLElement | ElementRef): StringMap { export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string { return attrs[typeof attr === 'string' ? attr.toLowerCase() : - attr.find(a => attrs[a.toLowerCase()] !== undefined) + attr.find(a => attrs[a.toLowerCase()] !== undefined) || '' ]; } @@ -33,7 +33,7 @@ export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): st * @param attrValue The string value of some attribute (or undefined if attribute not present) * @param def Default boolean value when attribute is undefined. */ -export function boolFromValue(attrValue: string, def: boolean = false) { +export function boolFromValue(attrValue: string|undefined, def: boolean = false) { // tslint:disable-next-line:triple-equals return attrValue == undefined ? def : attrValue.trim() !== 'false'; } diff --git a/aio/src/app/shared/copier.service.ts b/aio/src/app/shared/copier.service.ts index 301cffe465..e00a750ec2 100644 --- a/aio/src/app/shared/copier.service.ts +++ b/aio/src/app/shared/copier.service.ts @@ -9,7 +9,7 @@ export class CopierService { - private fakeElem: HTMLTextAreaElement; + private fakeElem: HTMLTextAreaElement|null; /** * Creates a fake textarea element, sets its value from `text` property, diff --git a/aio/src/app/shared/custom-icon-registry.spec.ts b/aio/src/app/shared/custom-icon-registry.spec.ts index 712d949ee3..83be81af2a 100644 --- a/aio/src/app/shared/custom-icon-registry.spec.ts +++ b/aio/src/app/shared/custom-icon-registry.spec.ts @@ -11,7 +11,7 @@ describe('CustomIconRegistry', () => { { name: 'test_icon', svgSource: svgSrc } ]; const registry = new CustomIconRegistry(mockHttp, mockSanitizer, svgIcons); - let svgElement: SVGElement; + let svgElement: SVGElement|undefined; registry.getNamedSvgIcon('test_icon').subscribe(el => svgElement = el); expect(svgElement).toEqual(createSvg(svgSrc)); }); @@ -36,8 +36,8 @@ describe('CustomIconRegistry', () => { }); }); -function createSvg(svgSrc) { +function createSvg(svgSrc: string): SVGElement { const div = document.createElement('div'); div.innerHTML = svgSrc; - return div.querySelector('svg'); + return div.querySelector('svg')!; } diff --git a/aio/src/app/shared/custom-icon-registry.ts b/aio/src/app/shared/custom-icon-registry.ts index cb974c4a2e..8ac0dbed9e 100644 --- a/aio/src/app/shared/custom-icon-registry.ts +++ b/aio/src/app/shared/custom-icon-registry.ts @@ -52,7 +52,7 @@ export class CustomIconRegistry extends MatIconRegistry { svgIcons.forEach(icon => { // SECURITY: the source for the SVG icons is provided in code by trusted developers div.innerHTML = icon.svgSource; - this.preloadedSvgElements[icon.name] = div.querySelector('svg'); + this.preloadedSvgElements[icon.name] = div.querySelector('svg')!; }); } } diff --git a/aio/src/app/shared/ga.service.ts b/aio/src/app/shared/ga.service.ts index 747e8285b4..ffd620cac5 100644 --- a/aio/src/app/shared/ga.service.ts +++ b/aio/src/app/shared/ga.service.ts @@ -29,7 +29,7 @@ export class GaService { this.ga('send', 'pageview'); } - ga(...args) { - this.window['ga'](...args); + ga(...args: any[]) { + (this.window as any)['ga'](...args); } } diff --git a/aio/src/app/shared/location.service.spec.ts b/aio/src/app/shared/location.service.spec.ts index 8781721819..0f1f09f875 100644 --- a/aio/src/app/shared/location.service.spec.ts +++ b/aio/src/app/shared/location.service.spec.ts @@ -39,7 +39,7 @@ describe('LocationService', () => { location.simulatePopState('/next-url2'); location.simulatePopState('/next-url3'); - let initialUrl; + let initialUrl: string|undefined; service.currentUrl.subscribe(url => initialUrl = url); expect(initialUrl).toEqual('next-url3'); }); @@ -49,7 +49,7 @@ describe('LocationService', () => { location.simulatePopState('/initial-url2'); location.simulatePopState('/initial-url3'); - const urls = []; + const urls: string[] = []; service.currentUrl.subscribe(url => urls.push(url)); location.simulatePopState('/next-url1'); @@ -69,13 +69,13 @@ describe('LocationService', () => { location.simulatePopState('/initial-url2'); location.simulatePopState('/initial-url3'); - const urls1 = []; + const urls1: string[] = []; service.currentUrl.subscribe(url => urls1.push(url)); location.simulatePopState('/next-url1'); location.simulatePopState('/next-url2'); - const urls2 = []; + const urls2: string[] = []; service.currentUrl.subscribe(url => urls2.push(url)); location.simulatePopState('/next-url3'); @@ -150,7 +150,7 @@ describe('LocationService', () => { }); it('should strip the query off the url', () => { - let path: string; + let path: string|undefined; service.currentPath.subscribe(p => path = p); @@ -182,7 +182,7 @@ describe('LocationService', () => { location.simulatePopState('/next/url2'); location.simulatePopState('/next/url3'); - let initialPath: string; + let initialPath: string|undefined; service.currentPath.subscribe(path => initialPath = path); expect(initialPath).toEqual('next/url3'); @@ -247,7 +247,7 @@ describe('LocationService', () => { }); it('should emit the new url', () => { - const urls = []; + const urls: string[] = []; service.go('some-initial-url'); service.currentUrl.subscribe(url => urls.push(url)); @@ -259,7 +259,7 @@ describe('LocationService', () => { }); it('should strip leading and trailing slashes', () => { - let url: string; + let url: string|undefined; service.currentUrl.subscribe(u => url = u); service.go('/some/url/'); @@ -269,23 +269,18 @@ describe('LocationService', () => { expect(url).toBe('some/url'); }); - it('should ignore undefined URL string', noUrlTest(undefined)); - it('should ignore null URL string', noUrlTest(null)); - it('should ignore empty URL string', noUrlTest('')); - function noUrlTest(testUrl: string) { - return function() { + it('should ignore empty URL string', () => { const initialUrl = 'some/url'; const goExternalSpy = spyOn(service, 'goExternal'); - let url: string; + let url: string|undefined; service.go(initialUrl); service.currentUrl.subscribe(u => url = u); - service.go(testUrl); + service.go(''); expect(url).toEqual(initialUrl, 'should not have re-navigated locally'); expect(goExternalSpy).not.toHaveBeenCalled(); - }; - } + }); it('should leave the site for external url that starts with "http"', () => { const goExternalSpy = spyOn(service, 'goExternal'); @@ -310,7 +305,7 @@ describe('LocationService', () => { }); it('should not update currentUrl for external url that starts with "http"', () => { - let localUrl: string; + let localUrl: string|undefined; spyOn(service, 'goExternal'); service.currentUrl.subscribe(url => localUrl = url); service.go('https://some/far/away/land'); diff --git a/aio/src/app/shared/location.service.ts b/aio/src/app/shared/location.service.ts index 456a1c1fc4..762a96838c 100644 --- a/aio/src/app/shared/location.service.ts +++ b/aio/src/app/shared/location.service.ts @@ -18,7 +18,7 @@ export class LocationService { .map(url => this.stripSlashes(url)); currentPath = this.currentUrl - .map(url => url.match(/[^?#]*/)[0]) // strip query and hash + .map(url => (url.match(/[^?#]*/) || [])[0]) // strip query and hash .do(path => this.gaService.locationChanged(path)); constructor( @@ -30,14 +30,14 @@ export class LocationService { this.urlSubject.next(location.path(true)); this.location.subscribe(state => { - return this.urlSubject.next(state.url); + return this.urlSubject.next(state.url || ''); }); swUpdates.updateActivated.subscribe(() => this.swUpdateActivated = true); } // TODO?: ignore if url-without-hash-or-search matches current location? - go(url: string) { + go(url: string|null|undefined) { if (!url) { return; } url = this.stripSlashes(url); if (/^http/.test(url) || this.swUpdateActivated) { @@ -62,8 +62,8 @@ export class LocationService { return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1'); } - search(): { [index: string]: string; } { - const search = {}; + search() { + const search: { [index: string]: string|undefined; } = {}; const path = this.location.path(); const q = path.indexOf('?'); if (q > -1) { @@ -80,11 +80,10 @@ export class LocationService { return search; } - setSearch(label: string, params: {}) { + setSearch(label: string, params: { [key: string]: string|undefined}) { const search = Object.keys(params).reduce((acc, key) => { const value = params[key]; - // tslint:disable-next-line:triple-equals - return value == undefined ? acc : + return (value === undefined) ? acc : acc += (acc ? '&' : '?') + `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; }, ''); diff --git a/aio/src/app/shared/logger.service.ts b/aio/src/app/shared/logger.service.ts index 99f1a55812..cf7c3479d3 100644 --- a/aio/src/app/shared/logger.service.ts +++ b/aio/src/app/shared/logger.service.ts @@ -5,17 +5,17 @@ import { environment } from '../../environments/environment'; @Injectable() export class Logger { - log(value: any, ...rest) { + log(value: any, ...rest: any[]) { if (!environment.production) { console.log(value, ...rest); } } - error(value: any, ...rest) { + error(value: any, ...rest: any[]) { console.error(value, ...rest); } - warn(value: any, ...rest) { + warn(value: any, ...rest: any[]) { console.warn(value, ...rest); } } diff --git a/aio/src/app/shared/scroll-spy.service.spec.ts b/aio/src/app/shared/scroll-spy.service.spec.ts index a7151e3845..675d1190d3 100644 --- a/aio/src/app/shared/scroll-spy.service.spec.ts +++ b/aio/src/app/shared/scroll-spy.service.spec.ts @@ -60,14 +60,15 @@ describe('ScrollSpiedElementGroup', () => { describe('#onScroll()', () => { let group: ScrollSpiedElementGroup; - let activeItems: ScrollItem[]; + let activeItems: (ScrollItem|null)[]; const activeIndices = () => activeItems.map(x => x && x.index); beforeEach(() => { const tops = [50, 150, 100]; - spyOn(ScrollSpiedElement.prototype, 'calculateTop').and.callFake(function(scrollTop, topOffset) { + spyOn(ScrollSpiedElement.prototype, 'calculateTop').and.callFake( + function(this: ScrollSpiedElement, scrollTop: number, topOffset: number) { this.top = tops[this.index]; }); @@ -234,7 +235,7 @@ describe('ScrollSpyService', () => { it('should remember and emit the last active item to new subscribers', () => { const items = [{index: 1}, {index: 2}, {index: 3}] as ScrollItem[]; - let lastActiveItem: ScrollItem | null; + let lastActiveItem: ScrollItem|null; const info = scrollSpyService.spyOn([]); const spiedElemGroup = getSpiedElemGroups()[0]; @@ -246,12 +247,12 @@ describe('ScrollSpyService', () => { spiedElemGroup.activeScrollItem.next(items[1]); info.active.subscribe(item => lastActiveItem = item); - expect(lastActiveItem).toBe(items[1]); + expect(lastActiveItem!).toBe(items[1]); spiedElemGroup.activeScrollItem.next(null); info.active.subscribe(item => lastActiveItem = item); - expect(lastActiveItem).toBeNull(); + expect(lastActiveItem!).toBeNull(); }); it('should only emit distinct values on `active`', () => { diff --git a/aio/src/app/shared/scroll-spy.service.ts b/aio/src/app/shared/scroll-spy.service.ts index 4ef2c6d534..9983fda5e6 100644 --- a/aio/src/app/shared/scroll-spy.service.ts +++ b/aio/src/app/shared/scroll-spy.service.ts @@ -102,7 +102,7 @@ export class ScrollSpiedElementGroup { * @param {number} maxScrollTop - The maximum possible `scrollTop` (based on the viewport size). */ onScroll(scrollTop: number, maxScrollTop: number) { - let activeItem: ScrollItem; + let activeItem: ScrollItem|undefined; if (scrollTop + 1 >= maxScrollTop) { activeItem = this.spiedElements[0]; @@ -112,6 +112,7 @@ export class ScrollSpiedElementGroup { activeItem = spiedElem; return true; } + return false; }); } diff --git a/aio/src/app/shared/scroll.service.spec.ts b/aio/src/app/shared/scroll.service.spec.ts index 4cf1379c0c..75dd14120b 100644 --- a/aio/src/app/shared/scroll.service.spec.ts +++ b/aio/src/app/shared/scroll.service.spec.ts @@ -111,7 +111,7 @@ describe('ScrollService', () => { const topOfPage = new MockElement(); document.getElementById.and - .callFake(id => id === 'top-of-page' ? topOfPage : null); + .callFake((id: string) => id === 'top-of-page' ? topOfPage : null); scrollService.scroll(); expect(topOfPage.scrollIntoView).toHaveBeenCalled(); @@ -201,7 +201,7 @@ describe('ScrollService', () => { it('should scroll to top', () => { const topOfPageElement = new MockElement(); document.getElementById.and.callFake( - id => id === 'top-of-page' ? topOfPageElement : null + (id: string) => id === 'top-of-page' ? topOfPageElement : null ); scrollService.scrollToTop(); diff --git a/aio/src/app/shared/scroll.service.ts b/aio/src/app/shared/scroll.service.ts index d2d1734d81..f7002a27d2 100644 --- a/aio/src/app/shared/scroll.service.ts +++ b/aio/src/app/shared/scroll.service.ts @@ -20,7 +20,7 @@ export class ScrollService { const toolbar = this.document.querySelector('.app-toolbar'); this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin; } - return this._topOffset; + return this._topOffset!; } get topOfPageElement() { @@ -54,7 +54,7 @@ export class ScrollService { * Scroll to the element. * Don't scroll if no element. */ - scrollToElement(element: Element) { + scrollToElement(element: Element|null) { if (element) { element.scrollIntoView(); diff --git a/aio/src/app/shared/search-results/search-results.component.spec.ts b/aio/src/app/shared/search-results/search-results.component.spec.ts index 335f74b9e3..602763b9d7 100644 --- a/aio/src/app/shared/search-results/search-results.component.spec.ts +++ b/aio/src/app/shared/search-results/search-results.component.spec.ts @@ -30,8 +30,8 @@ describe('SearchResultsComponent', () => { return take === undefined ? results : results.slice(0, take); } - function compareTitle(l: {title: string}, r: {title: string}) { - return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1; + function compareTitle(l: SearchResult, r: SearchResult) { + return l.title!.toUpperCase() > r.title!.toUpperCase() ? 1 : -1; } function setSearchResults(query: string, results: SearchResult[]) { @@ -117,7 +117,7 @@ describe('SearchResultsComponent', () => { it('should omit search results with no title', () => { const results = [ - { path: 'news', title: undefined, type: 'marketing', keywords: '', titleWords: '' } + { path: 'news', title: '', type: 'marketing', keywords: '', titleWords: '' } ]; setSearchResults('something', results); @@ -131,11 +131,11 @@ describe('SearchResultsComponent', () => { describe('when a search result anchor is clicked', () => { let searchResult: SearchResult; - let selected: SearchResult; + let selected: SearchResult|null; let anchor: DebugElement; beforeEach(() => { - component.resultSelected.subscribe(result => selected = result); + component.resultSelected.subscribe((result: SearchResult) => selected = result); selected = null; searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }; diff --git a/aio/src/app/shared/search-results/search-results.component.ts b/aio/src/app/shared/search-results/search-results.component.ts index 837459f344..9423fa9556 100644 --- a/aio/src/app/shared/search-results/search-results.component.ts +++ b/aio/src/app/shared/search-results/search-results.component.ts @@ -44,7 +44,7 @@ export class SearchResultsComponent implements OnChanges { return []; } this.notFoundMessage = 'No results found.'; - const searchAreaMap = {}; + const searchAreaMap: { [key: string]: SearchResult[] } = {}; search.results.forEach(result => { if (!result.title) { return; } // bad data; should fix const areaName = this.computeAreaName(result) || this.defaultArea; diff --git a/aio/src/app/shared/select/select.component.spec.ts b/aio/src/app/shared/select/select.component.spec.ts index d81abbadcb..c21b55dd65 100644 --- a/aio/src/app/shared/select/select.component.spec.ts +++ b/aio/src/app/shared/select/select.component.spec.ts @@ -37,10 +37,10 @@ describe('SelectComponent', () => { describe('button', () => { it('should display the label if provided', () => { - expect(getButton().textContent.trim()).toEqual(''); + expect(getButton().textContent!.trim()).toEqual(''); host.label = 'Label:'; fixture.detectChanges(); - expect(getButton().textContent.trim()).toEqual('Label:'); + expect(getButton().textContent!.trim()).toEqual('Label:'); }); it('should contain a symbol `` if hasSymbol is true', () => { @@ -49,7 +49,7 @@ describe('SelectComponent', () => { fixture.detectChanges(); const span = getButton().querySelector('span'); expect(span).not.toEqual(null); - expect(span.className).toContain('symbol'); + expect(span!.className).toContain('symbol'); }); it('should display the selected option, if there is one', () => { @@ -57,7 +57,7 @@ describe('SelectComponent', () => { host.selected = options[0]; fixture.detectChanges(); expect(getButton().textContent).toContain(options[0].title); - expect(getButton().querySelector('span').className).toContain(options[0].value); + expect(getButton().querySelector('span')!.className).toContain(options[0].value); }); it('should toggle the visibility of the options list when clicked', () => { @@ -90,7 +90,7 @@ describe('SelectComponent', () => { fixture.detectChanges(); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(getButton().textContent).toContain(options[0].title); - expect(getButton().querySelector('span').className).toContain(options[0].value); + expect(getButton().querySelector('span')!.className).toContain(options[0].value); }); it('should select the current option when enter is pressed', () => { @@ -99,7 +99,7 @@ describe('SelectComponent', () => { fixture.detectChanges(); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(getButton().textContent).toContain(options[0].title); - expect(getButton().querySelector('span').className).toContain(options[0].value); + expect(getButton().querySelector('span')!.className).toContain(options[0].value); }); it('should select the current option when space is pressed', () => { @@ -108,7 +108,7 @@ describe('SelectComponent', () => { fixture.detectChanges(); expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 }); expect(getButton().textContent).toContain(options[0].title); - expect(getButton().querySelector('span').className).toContain(options[0].value); + expect(getButton().querySelector('span')!.className).toContain(options[0].value); }); it('should hide when an option is clicked', () => { @@ -155,7 +155,7 @@ function getButton(): HTMLButtonElement { return element.query(By.css('button')).nativeElement; } -function getOptionContainer(): HTMLUListElement { +function getOptionContainer(): HTMLUListElement|null { const de = element.query(By.css('ul')); return de && de.nativeElement; } diff --git a/aio/src/app/shared/toc.service.spec.ts b/aio/src/app/shared/toc.service.spec.ts index 7827922868..8b19fc9371 100644 --- a/aio/src/app/shared/toc.service.spec.ts +++ b/aio/src/app/shared/toc.service.spec.ts @@ -35,8 +35,8 @@ describe('TocService', () => { it('should emit the latest value to new subscribers', () => { const expectedValue1 = tocItem('Heading A'); const expectedValue2 = tocItem('Heading B'); - let value1: TocItem[]; - let value2: TocItem[]; + let value1: TocItem[]|undefined; + let value2: TocItem[]|undefined; tocService.tocList.next([]); tocService.tocList.subscribe(v => value1 = v); @@ -235,22 +235,22 @@ describe('TocService', () => { }); it('should have href with docId and heading\'s id', () => { - const tocItem = lastTocList.find(item => item.title === 'Heading one'); + const tocItem = lastTocList.find(item => item.title === 'Heading one')!; expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`); }); it('should have level "h1" for an

', () => { - const tocItem = lastTocList.find(item => item.title === 'Fun with TOC'); + const tocItem = lastTocList.find(item => item.title === 'Fun with TOC')!; expect(tocItem.level).toEqual('h1'); }); it('should have level "h2" for an

', () => { - const tocItem = lastTocList.find(item => item.title === 'Heading one'); + const tocItem = lastTocList.find(item => item.title === 'Heading one')!; expect(tocItem.level).toEqual('h2'); }); it('should have level "h3" for an

', () => { - const tocItem = lastTocList.find(item => item.title === 'H3 3a'); + const tocItem = lastTocList.find(item => item.title === 'H3 3a')!; expect(tocItem.level).toEqual('h3'); }); @@ -273,7 +273,7 @@ describe('TocService', () => { }); it('should have href with docId and calculated heading id', () => { - const tocItem = lastTocList.find(item => item.title === 'H2 Two'); + const tocItem = lastTocList.find(item => item.title === 'H2 Two')!; expect(tocItem.href).toEqual(`${docId}#h2-two`); }); @@ -343,7 +343,7 @@ interface TestSafeHtml extends SafeHtml { class TestDomSanitizer { bypassSecurityTrustHtml = jasmine.createSpy('bypassSecurityTrustHtml') - .and.callFake(html => { + .and.callFake((html: string) => { return { changingThisBreaksApplicationSecurity: html, getTypeName: () => 'HTML', diff --git a/aio/src/app/shared/toc.service.ts b/aio/src/app/shared/toc.service.ts index 45dd96c2c3..a321f9b9fb 100644 --- a/aio/src/app/shared/toc.service.ts +++ b/aio/src/app/shared/toc.service.ts @@ -37,7 +37,7 @@ export class TocService { content: this.extractHeadingSafeHtml(heading), href: `${docId}#${this.getId(heading, idMap)}`, level: heading.tagName.toLowerCase(), - title: heading.textContent.trim(), + title: (heading.textContent || '').trim(), })); this.tocList.next(tocList); @@ -87,7 +87,7 @@ export class TocService { if (id) { addToMap(id); } else { - id = h.textContent.trim().toLowerCase().replace(/\W+/g, '-'); + id = (h.textContent || '').trim().toLowerCase().replace(/\W+/g, '-'); id = addToMap(id); h.id = id; } @@ -95,7 +95,9 @@ export class TocService { // Map guards against duplicate id creation. function addToMap(key: string) { - const count = idMap[key] = idMap[key] ? idMap[key] + 1 : 1; + const oldCount = idMap.get(key) || 0; + const count = oldCount + 1; + idMap.set(key, count); return count === 1 ? key : `${key}-${count}`; } } diff --git a/aio/src/app/sw-updates/sw-updates.service.spec.ts b/aio/src/app/sw-updates/sw-updates.service.spec.ts index 435e03c0fb..ff4bb5b714 100644 --- a/aio/src/app/sw-updates/sw-updates.service.spec.ts +++ b/aio/src/app/sw-updates/sw-updates.service.spec.ts @@ -31,7 +31,7 @@ describe('SwUpdatesService', () => { checkInterval = (service as any).checkInterval; }; const tearDown = () => service.ngOnDestroy(); - const run = specFn => () => { + const run = (specFn: VoidFunction) => () => { setup(); specFn(); tearDown(); @@ -90,7 +90,7 @@ describe('SwUpdatesService', () => { }))); it('should emit on `updateActivated` when an update has been activated', run(() => { - const activatedVersions: string[] = []; + const activatedVersions: (string|undefined)[] = []; service.updateActivated.subscribe(v => activatedVersions.push(v)); sw.$$updatesSubj.next({type: 'pending', version: 'foo'}); @@ -126,7 +126,7 @@ describe('SwUpdatesService', () => { }))); it('should stop emitting on `updateActivated`', run(() => { - const activatedVersions: string[] = []; + const activatedVersions: (string|undefined)[] = []; service.updateActivated.subscribe(v => activatedVersions.push(v)); sw.$$updatesSubj.next({type: 'pending', version: 'foo'}); diff --git a/aio/src/app/sw-updates/sw-updates.service.ts b/aio/src/app/sw-updates/sw-updates.service.ts index 0580be2f8d..8cc3bdf4d6 100644 --- a/aio/src/app/sw-updates/sw-updates.service.ts +++ b/aio/src/app/sw-updates/sw-updates.service.ts @@ -51,7 +51,7 @@ export class SwUpdatesService implements OnDestroy { private activateUpdate() { this.log('Activating update...'); - this.sw.activateUpdate(null) + this.sw.activateUpdate(null as any) // expects a non-null string .subscribe(() => this.scheduleCheckForUpdate()); } diff --git a/aio/src/testing/doc-viewer-utils.ts b/aio/src/testing/doc-viewer-utils.ts index 75bd3e7d86..41f27aa131 100644 --- a/aio/src/testing/doc-viewer-utils.ts +++ b/aio/src/testing/doc-viewer-utils.ts @@ -38,7 +38,7 @@ export class TestDocViewerComponent extends DocViewerComponent { template: 'Test Component', }) export class TestParentComponent { - currentDoc: DocumentContents; + currentDoc?: DocumentContents|null; @ViewChild(DocViewerComponent) docViewer: DocViewerComponent; } @@ -77,7 +77,7 @@ export class TestModule { } export class ObservableWithSubscriptionSpies extends Observable { unsubscribeSpies: jasmine.Spy[] = []; - subscribeSpy = spyOn(this, 'subscribe').and.callFake((...args) => { + subscribeSpy = spyOn(this, 'subscribe').and.callFake((...args: any[]) => { const subscription = super.subscribe(...args); const unsubscribeSpy = spyOn(subscription, 'unsubscribe').and.callThrough(); this.unsubscribeSpies.push(unsubscribeSpy); diff --git a/aio/src/testing/embed-components-utils.ts b/aio/src/testing/embed-components-utils.ts index fc8f7af566..898e8dc92b 100644 --- a/aio/src/testing/embed-components-utils.ts +++ b/aio/src/testing/embed-components-utils.ts @@ -113,7 +113,7 @@ export class MockNgModuleFactoryLoader implements NgModuleFactoryLoader { this.loadedPaths.push(path); const platformRef = getPlatform(); - const compilerFactory = platformRef.injector.get(CompilerFactory) as CompilerFactory; + const compilerFactory = platformRef!.injector.get(CompilerFactory) as CompilerFactory; const compiler = compilerFactory.createCompiler([]); return compiler.compileModuleAsync(MockEmbeddedModule); diff --git a/aio/src/testing/location.service.ts b/aio/src/testing/location.service.ts index 1e03a69626..78fabd3ae0 100644 --- a/aio/src/testing/location.service.ts +++ b/aio/src/testing/location.service.ts @@ -4,7 +4,7 @@ export class MockLocationService { urlSubject = new BehaviorSubject(this.initialUrl); currentUrl = this.urlSubject.asObservable().map(url => this.stripSlashes(url)); // strip off query and hash - currentPath = this.currentUrl.map(url => url.match(/[^?#]*/)[0]); + currentPath = this.currentUrl.map(url => url.match(/[^?#]*/)![0]); search = jasmine.createSpy('search').and.returnValue({}); setSearch = jasmine.createSpy('setSearch'); go = jasmine.createSpy('Location.go').and @@ -14,7 +14,7 @@ export class MockLocationService { handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick') .and.returnValue(false); // prevent click from causing a browser navigation - constructor(private initialUrl) {} + constructor(private initialUrl: string) {} private stripSlashes(url: string) { return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1'); diff --git a/aio/src/testing/logger.service.ts b/aio/src/testing/logger.service.ts index 999526c402..f1aa4de6fa 100644 --- a/aio/src/testing/logger.service.ts +++ b/aio/src/testing/logger.service.ts @@ -3,21 +3,21 @@ import { Injectable } from '@angular/core'; @Injectable() export class MockLogger { - output = { + output: { log: any[], error: any[], warn: any[] } = { log: [], error: [], warn: [] }; - log(value: any, ...rest) { + log(value: any, ...rest: any[]) { this.output.log.push([value, ...rest]); } - error(value: any, ...rest) { + error(value: any, ...rest: any[]) { this.output.error.push([value, ...rest]); } - warn(value: any, ...rest) { + warn(value: any, ...rest: any[]) { this.output.warn.push([value, ...rest]); } } diff --git a/aio/tsconfig.json b/aio/tsconfig.json index b8ce16de73..64194c0cc1 100644 --- a/aio/tsconfig.json +++ b/aio/tsconfig.json @@ -1,6 +1,8 @@ { "compileOnSave": false, "compilerOptions": { + "strict": true, + "noImplicitAny": false, "outDir": "./dist/out-tsc", "baseUrl": "src", "sourceMap": true,