refactor(docs-infra): enable tslint rules for the angular.io app (#39307)

Signed-off-by: Tasos Alexiou <tasos@arrikto.com>

PR Close #39307
This commit is contained in:
Tasos Alexiou 2020-10-16 21:46:24 +03:00 committed by Joey Perrott
parent c53bae839d
commit 356a256909
27 changed files with 88 additions and 98 deletions

View File

@ -635,61 +635,53 @@ describe('AppComponent', () => {
});
describe('aio-toc', () => {
let tocContainer: HTMLElement|null;
let toc: HTMLElement|null;
const setHasFloatingToc = (hasFloatingToc: boolean) => {
function setHasFloatingTocAndGetToc(hasFloatingToc: false): [null, null];
function setHasFloatingTocAndGetToc(hasFloatingToc: true): [HTMLElement, HTMLElement];
function setHasFloatingTocAndGetToc(hasFloatingToc: boolean) {
component.hasFloatingToc = hasFloatingToc;
fixture.detectChanges();
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
toc = tocContainer && tocContainer.querySelector('aio-toc');
};
const tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
const toc = tocContainer && tocContainer.querySelector('aio-toc');
beforeEach(() => {
tocContainer = null;
toc = null;
});
return [toc, tocContainer];
}
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
expect(tocContainer).toBeFalsy();
expect(toc).toBeFalsy();
const [toc1, tocContainer1] = setHasFloatingTocAndGetToc(true);
expect(tocContainer1).toBeTruthy();
expect(toc1).toBeTruthy();
setHasFloatingToc(true);
expect(tocContainer).toBeTruthy();
expect(toc).toBeTruthy();
setHasFloatingToc(false);
expect(tocContainer).toBeFalsy();
expect(toc).toBeFalsy();
const [toc2, tocContainer2] = setHasFloatingTocAndGetToc(false);
expect(tocContainer2).toBeFalsy();
expect(toc2).toBeFalsy();
});
it('should have a non-embedded `<aio-toc>` element', () => {
setHasFloatingToc(true);
expect(toc!.classList.contains('embedded')).toBe(false);
const [toc] = setHasFloatingTocAndGetToc(true);
expect(toc.classList.contains('embedded')).toBe(false);
});
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
setHasFloatingToc(true);
const [, tocContainer] = setHasFloatingTocAndGetToc(true);
component.tocMaxHeight = '100';
fixture.detectChanges();
expect(tocContainer!.style.maxHeight).toBe('100px');
expect(tocContainer.style.maxHeight).toBe('100px');
component.tocMaxHeight = '200';
fixture.detectChanges();
expect(tocContainer!.style.maxHeight).toBe('200px');
expect(tocContainer.style.maxHeight).toBe('200px');
});
it('should restrain scrolling inside the ToC container', () => {
const restrainScrolling = spyOn(component, 'restrainScrolling');
const evt = new WheelEvent('wheel');
const [, tocContainer] = setHasFloatingTocAndGetToc(true);
setHasFloatingToc(true);
expect(restrainScrolling).not.toHaveBeenCalled();
tocContainer!.dispatchEvent(evt);
tocContainer.dispatchEvent(evt);
expect(restrainScrolling).toHaveBeenCalledWith(evt);
});
@ -697,7 +689,7 @@ describe('AppComponent', () => {
const loader = fixture.debugElement.injector.get(ElementsLoader) as unknown as TestElementsLoader;
expect(loader.loadCustomElement).not.toHaveBeenCalled();
setHasFloatingToc(true);
setHasFloatingTocAndGetToc(true);
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
});
});
@ -721,7 +713,7 @@ describe('AppComponent', () => {
createTestingModule('a/b', 'stable');
await initializeTest();
const banner: HTMLElement = fixture.debugElement.query(By.css('aio-mode-banner')).nativeElement;
expect(banner.textContent!.trim()).toEqual('');
expect(banner.textContent?.trim()).toEqual('');
});
});
@ -1355,10 +1347,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') ? '' : `<h1 class="no-toc">${title}</h1>`;
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
data = { id, contents };

View File

@ -163,7 +163,7 @@ export class AppComponent implements OnInit {
// 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}`) as NavigationNode;
this.currentDocVersion.title += ` (v${versionInfo.raw})`;
});

View File

@ -102,11 +102,11 @@ describe('AnnouncementBarComponent', () => {
});
it('should display an image', () => {
expect(element.querySelector('img')!.src).toContain('dummy/image');
expect(element.querySelector('img')?.src).toContain('dummy/image');
});
it('should display a link', () => {
expect(element.querySelector('a')!.href).toContain('link/to/website');
expect(element.querySelector('a')?.href).toContain('link/to/website');
});
});
});

View File

@ -38,7 +38,7 @@ describe('ApiListComponent', () => {
component.filteredSections.subscribe(filtered => {
filtered = filtered.filter(section => section.items);
expect(filtered.length).toBeGreaterThan(0, 'expected something');
expect(filtered.every(section => section.items!.every(itemTest))).toBe(true, label);
expect(filtered.every(section => section.items?.every(itemTest))).toBe(true, label);
});
}
@ -66,7 +66,7 @@ describe('ApiListComponent', () => {
filtered = filtered.filter(section => Array.isArray(section.items));
expect(filtered.length).toBe(1, 'only one section');
expect(filtered[0].name).toBe('core');
expect(filtered[0].items).toEqual(sections.find(section => section.name === 'core')!.items);
expect(filtered[0].items).toEqual(sections.find(section => section.name === 'core')?.items as ApiItem[]);
});
});
@ -74,8 +74,8 @@ describe('ApiListComponent', () => {
it('should null if there are no matching items and the section itself does not match', () => {
component.setQuery('core');
component.filteredSections.subscribe(filtered => {
const commonSection = filtered.find(section => section.name === 'common')!;
expect(commonSection.items).toBe(null);
const commonSection = filtered.find(section => section.name === 'common');
expect(commonSection?.items).toBe(null);
});
});
@ -117,7 +117,7 @@ describe('ApiListComponent', () => {
filtered = filtered.filter(s => s.items);
expect(filtered.length).toBe(1, 'sections');
expect(filtered[0].name).toBe(section, 'section name');
const items = filtered[0].items!;
const items = filtered[0].items as ApiItem[];
expect(items.length).toBe(1, 'items');
const item = items[0];

View File

@ -17,9 +17,9 @@ import { Option } from 'app/shared/select/select.component';
import { map } from 'rxjs/operators';
class SearchCriteria {
query ? = '';
status ? = 'all';
type ? = 'all';
query = '';
status = 'all';
type = 'all';
}
@Component({
@ -116,13 +116,13 @@ export class ApiListComponent implements OnInit {
const sectionNameMatches = !query || section.name.indexOf(query) !== -1;
const matchesQuery = (item: ApiItem) =>
sectionNameMatches || item.name.indexOf(query!) !== -1;
sectionNameMatches || item.name.indexOf(query) !== -1;
const matchesStatus = (item: ApiItem) =>
status === 'all' || status === item.stability || (status === 'security-risk' && item.securityRisk);
const matchesType = (item: ApiItem) =>
type === 'all' || type === item.docType;
const items = section.items!.filter(item =>
const items: ApiItem[] = (section.items || []).filter(item =>
matchesType(item) && matchesStatus(item) && matchesQuery(item));
// If there are no items we still return an empty array if the section name matches and the type is 'package'
@ -160,7 +160,7 @@ export class ApiListComponent implements OnInit {
this.locationService.setSearch('API Search', params);
}
private setSearchCriteria(criteria: SearchCriteria) {
private setSearchCriteria(criteria: Partial<SearchCriteria>) {
this.criteriaSubject.next(Object.assign(this.searchCriteria, criteria));
this.setLocationSearch();
}

View File

@ -251,7 +251,7 @@ describe('CodeComponent', () => {
actualCode = spy.calls.mostRecent().args[0];
expect(actualCode).toBe(expectedCode, `when linenums=${linenums}`);
expect(actualCode.match(/\r?\n/g)!.length).toBe(5);
expect(actualCode.match(/\r?\n/g)?.length).toBe(5);
spy.calls.reset();
});

View File

@ -45,12 +45,12 @@ export class ElementsLoader {
loadCustomElement(selector: string): Promise<void> {
if (this.elementsLoading.has(selector)) {
// The custom element is in the process of being loaded and registered.
return this.elementsLoading.get(selector)!;
return this.elementsLoading.get(selector) as Promise<void>;
}
if (this.elementsToLoad.has(selector)) {
// Load and register the custom element (for the first time).
const modulePathLoader = this.elementsToLoad.get(selector)!;
const modulePathLoader = this.elementsToLoad.get(selector) as LoadChildrenCallback;
const loadedAndRegistered =
(modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>)
.then(elementModuleOrFactory => {
@ -73,7 +73,7 @@ export class ElementsLoader {
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
const CustomElement = createCustomElement(CustomElementComponent, {injector});
customElements!.define(selector, CustomElement);
customElements.define(selector, CustomElement);
return customElements.whenDefined(selector);
})
.then(() => {

View File

@ -337,7 +337,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();

View File

@ -49,7 +49,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) as Observable<DocumentContents>;
}
private fetchDocument(id: string): Observable<DocumentContents> {

View File

@ -213,10 +213,10 @@ describe('DocViewerComponent', () => {
describe('needed', () => {
it('should add an embedded ToC element if there is an `<h1>` heading', () => {
doPrepareTitleAndToc(DOC_WITH_H1);
const tocEl = getTocEl()!;
const tocEl = getTocEl();
expect(tocEl).toBeTruthy();
expect(tocEl.classList.contains('embedded')).toBe(true);
expect(tocEl?.classList.contains('embedded')).toBe(true);
});
it('should not add a second ToC element if there a hard coded one in place', () => {

View File

@ -98,9 +98,9 @@ export class DocViewerComponent implements OnDestroy {
const needsToc = !!titleEl && !/no-?toc/i.test(titleEl.className);
const embeddedToc = targetElem.querySelector('aio-toc.embedded');
if (needsToc && !embeddedToc) {
if (titleEl && needsToc && !embeddedToc) {
// Add an embedded ToC if it's needed and there isn't one in the content already.
titleEl!.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');
titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');
} else if (!needsToc && embeddedToc && embeddedToc.parentNode !== null) {
// Remove the embedded Toc if it's there and not needed.
// We cannot use ChildNode.remove() because of IE11
@ -223,7 +223,7 @@ export class DocViewerComponent implements OnDestroy {
done$ = done$.pipe(
// Remove the current view from the viewer.
switchMap(() => animateLeave(this.currViewContainer)),
tap(() => this.currViewContainer.parentElement!.removeChild(this.currViewContainer)),
tap(() => (this.currViewContainer.parentElement as HTMLElement).removeChild(this.currViewContainer)),
tap(() => this.docRemoved.emit()),
);
}

View File

@ -144,7 +144,7 @@ describe('NavigationService', () => {
url: 'b',
view: 'SideNav',
nodes: [
sideNavNodes[0].children![0],
sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0]
]
}
@ -156,8 +156,8 @@ describe('NavigationService', () => {
url: 'd',
view: 'SideNav',
nodes: [
sideNavNodes[0].children![0].children![1],
sideNavNodes[0].children![0],
sideNavNodes[0].children?.[0].children?.[1] as NavigationNode,
sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0]
]
}
@ -201,8 +201,8 @@ describe('NavigationService', () => {
url: 'c',
view: 'SideNav',
nodes: [
sideNavNodes[0].children![0].children![0],
sideNavNodes[0].children![0],
sideNavNodes[0].children?.[0].children?.[0] as NavigationNode,
sideNavNodes[0].children?.[0] as NavigationNode,
sideNavNodes[0]
]
}

View File

@ -153,7 +153,7 @@ export class NavigationService {
if (!navMap.has(cleanedUrl)) {
navMap.set(cleanedUrl, {});
}
const navMapItem = navMap.get(cleanedUrl)!;
const navMapItem = navMap.get(cleanedUrl) as CurrentNodes;
navMapItem[view] = { url, view, nodes };
}

View File

@ -8,7 +8,7 @@ describe('Attribute Utilities', () => {
beforeEach(() => {
const div = document.createElement('div');
div.innerHTML = `<div a b="true" c="false" D="foo" d-E></div>`;
testEl = div.querySelector('div')!;
testEl = div.querySelector('div') as HTMLElement;
});
describe('getAttrs', () => {

View File

@ -44,7 +44,7 @@ export class CopierService {
* @return The temporary `<textarea>` element containing the specified text.
*/
private createTextArea(text: string): HTMLTextAreaElement {
const docElem = document.documentElement!;
const docElem = document.documentElement;
const isRTL = docElem.getAttribute('dir') === 'rtl';
// Create a temporary element to hold the contents to copy.

View File

@ -65,5 +65,5 @@ describe('CustomIconRegistry', () => {
function createSvg(svgSrc: string): SVGElement {
const div = document.createElement('div');
div.innerHTML = svgSrc;
return div.querySelector('svg')!;
return div.querySelector('svg') as SVGElement;
}

View File

@ -78,7 +78,7 @@ export class CustomIconRegistry extends MatIconRegistry {
// SECURITY: the source for the SVG icons is provided in code by trusted developers
div.innerHTML = svgIcon.svgSource;
const svgElement = div.querySelector('svg')!;
const svgElement = div.querySelector('svg') as SVGElement;
nsIconMap[svgIcon.name] = svgElement;
return svgElement;

View File

@ -235,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 = null as unknown as ScrollItem|null;
const info = scrollSpyService.spyOn([]);
const spiedElemGroup = getSpiedElemGroups()[0];
@ -247,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`', () => {

View File

@ -114,7 +114,7 @@ describe('ScrollService', () => {
it('should not break when cookies are disabled in the browser', () => {
expect(() => {
const originalSessionStorage = Object.getOwnPropertyDescriptor(window, 'sessionStorage')!;
const originalSessionStorage = Object.getOwnPropertyDescriptor(window, 'sessionStorage') as PropertyDescriptor;
try {
// Simulate `window.sessionStorage` being inaccessible, when cookies are disabled.

View File

@ -33,7 +33,7 @@ export class ScrollService implements OnDestroy {
const toolbar = this.document.querySelector('.app-toolbar');
this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
}
return this._topOffset!;
return this._topOffset as number;
}
get topOfPageElement() {

View File

@ -98,10 +98,10 @@ function splitPages(allPages: SearchResult[]) {
}
});
while (priorityPages.length < 5 && pages.length) {
priorityPages.push(pages.shift()!);
priorityPages.push(pages.shift() as SearchResult);
}
while (priorityPages.length < 5 && deprecated.length) {
priorityPages.push(deprecated.shift()!);
priorityPages.push(deprecated.shift() as SearchResult);
}
pages.sort(compareResults);

View File

@ -35,10 +35,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', () => {
@ -53,7 +53,7 @@ describe('SelectComponent', () => {
host.selected = options[0];
fixture.detectChanges();
expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value);
expect(getButtonSymbol()?.className).toContain(options[0].value);
});
it('should toggle the visibility of the options list when clicked', () => {
@ -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(getButtonSymbol()!.className).toContain(options[0].value);
expect(getButtonSymbol()?.className).toContain(options[0].value);
});
it('should select the current option when enter is pressed', () => {
@ -117,7 +117,7 @@ describe('SelectComponent', () => {
fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value);
expect(getButtonSymbol()?.className).toContain(options[0].value);
});
it('should select the current option when space is pressed', () => {
@ -126,7 +126,7 @@ describe('SelectComponent', () => {
fixture.detectChanges();
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
expect(getButton().textContent).toContain(options[0].title);
expect(getButtonSymbol()!.className).toContain(options[0].value);
expect(getButtonSymbol()?.className).toContain(options[0].value);
});
it('should hide when an option is clicked', () => {

View File

@ -237,23 +237,23 @@ describe('TocService', () => {
});
it('should have href with docId and heading\'s id', () => {
const tocItem = lastTocList.find(item => item.title === 'Heading one')!;
expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`);
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 <h1>', () => {
const tocItem = lastTocList.find(item => item.title === 'Fun with TOC')!;
expect(tocItem.level).toEqual('h1');
const tocItem = lastTocList.find(item => item.title === 'Fun with TOC');
expect(tocItem?.level).toEqual('h1');
});
it('should have level "h2" for an <h2>', () => {
const tocItem = lastTocList.find(item => item.title === 'Heading one')!;
expect(tocItem.level).toEqual('h2');
const tocItem = lastTocList.find(item => item.title === 'Heading one');
expect(tocItem?.level).toEqual('h2');
});
it('should have level "h3" for an <h3>', () => {
const tocItem = lastTocList.find(item => item.title === 'H3 3a')!;
expect(tocItem.level).toEqual('h3');
const tocItem = lastTocList.find(item => item.title === 'H3 3a');
expect(tocItem?.level).toEqual('h3');
});
it('should have title which is heading\'s textContent ', () => {
@ -275,8 +275,8 @@ describe('TocService', () => {
});
it('should have href with docId and calculated heading id', () => {
const tocItem = lastTocList.find(item => item.title === 'H2 Two')!;
expect(tocItem.href).toEqual(`${docId}#h2-two`);
const tocItem = lastTocList.find(item => item.title === 'H2 Two');
expect(tocItem?.href).toEqual(`${docId}#h2-two`);
});
it('should ignore HTML in heading when calculating id', () => {

View File

@ -70,7 +70,7 @@ export class TocService {
// Remove any remaining `a` elements (but keep their content).
querySelectorAll(div, 'a').forEach(anchorLink => {
// We want to keep the content of this anchor, so move it into its parent.
const parent = anchorLink.parentNode!;
const parent = anchorLink.parentNode as Node;
while (anchorLink.childNodes.length) {
parent.insertBefore(anchorLink.childNodes[0], anchorLink);
}

View File

@ -5,7 +5,7 @@ export class MockLocationService {
urlSubject = new BehaviorSubject<string>(this.initialUrl);
currentUrl = this.urlSubject.asObservable().pipe(map(url => this.stripSlashes(url)));
// strip off query and hash
currentPath = this.currentUrl.pipe(map(url => url.match(/[^?#]*/)![0]));
currentPath = this.currentUrl.pipe(map(url => url.match(/[^?#]*/)?.[0] || ''));
search = jasmine.createSpy('search').and.returnValue({});
setSearch = jasmine.createSpy('setSearch');
fullPageNavigationNeeded = jasmine.createSpy('Location.fullPageNavigationNeeded');
@ -23,4 +23,3 @@ export class MockLocationService {
return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1');
}
}

View File

@ -36,7 +36,7 @@ describe('site App', () => {
// Test all headings (and sub-headings).
expect(await navItemHeadings.count()).toBeGreaterThan(0);
await navItemHeadings.each(heading => testNavItemHeading(heading!, 1));
await navItemHeadings.each(heading => heading && testNavItemHeading(heading, 1));
// Helpers
async function expectToBeCollapsed(elementFinder: ElementFinder) {
@ -63,7 +63,7 @@ describe('site App', () => {
// Recursively test child-headings (while this heading is expanded).
const nextLevel = level + 1;
const childNavItemHeadings = page.getNavItemHeadings(children, nextLevel);
await childNavItemHeadings.each(childHeading => testNavItemHeading(childHeading!, nextLevel));
await childNavItemHeadings.each(childHeading => childHeading && testNavItemHeading(childHeading, nextLevel));
// Ensure heading does not cause navigation when collapsing.
await page.click(heading);

View File

@ -61,8 +61,7 @@
true,
"ignore-params"
],
// TODO(gkalpak): Fix the code and enable this to align with CLI. (Failures: 59)
// "no-non-null-assertion": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,