refactor(docs-infra): introduce max-len 120 eslint rule (#43439)

add the max-len rule to the aio eslintrc and fix what code
breaks such rule

PR Close #43439
This commit is contained in:
dario-piotrowicz
2021-09-13 22:31:04 +01:00
committed by Andrew Kushnir
parent 4ef9d247b6
commit 66c6dbdc79
21 changed files with 252 additions and 60 deletions
+1
View File
@@ -27,6 +27,7 @@
"style": "kebab-case"
}
],
"max-len": ["error" , 120],
"@angular-eslint/directive-selector": [
"error",
{
+28 -6
View File
@@ -431,8 +431,14 @@ describe('AppComponent', () => {
await setupSelectorForTesting();
locationService.urlSubject.next('docs#section-1');
const versionWithoutSlashIndex = component.docVersions.length;
const versionWithoutSlashUrl = component.docVersions[versionWithoutSlashIndex] = { url: 'https://next.angular.io', title: 'foo' };
selectElement.triggerEventHandler('change', { option: versionWithoutSlashUrl, index: versionWithoutSlashIndex });
const versionWithoutSlashUrl = (component.docVersions[versionWithoutSlashIndex] = {
url: 'https://next.angular.io',
title: 'foo',
});
selectElement.triggerEventHandler('change', {
option: versionWithoutSlashUrl,
index: versionWithoutSlashIndex,
});
expect(locationService.go).toHaveBeenCalledWith('https://next.angular.io/docs#section-1');
});
});
@@ -589,7 +595,10 @@ describe('AppComponent', () => {
});
describe('restrainScrolling()', () => {
const preventedScrolling = (currentTarget: { scrollTop: number, scrollHeight?: number, clientHeight?: number }, deltaY: number) => {
const preventedScrolling = (
currentTarget: {scrollTop: number, scrollHeight?: number, clientHeight?: number},
deltaY: number
) => {
const evt = {
deltaY,
currentTarget,
@@ -780,15 +789,20 @@ describe('AppComponent', () => {
describe('keyup handling', () => {
it('should grab focus when the / key is pressed', () => {
const searchBox: SearchBoxComponent = fixture.debugElement.query(By.directive(SearchBoxComponent)).componentInstance;
const searchBox: SearchBoxComponent = fixture.debugElement.query(
By.directive(SearchBoxComponent)
).componentInstance;
spyOn(searchBox, 'focus');
window.document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' }));
fixture.detectChanges();
expect(searchBox.focus).toHaveBeenCalled();
});
// eslint-disable-next-line max-len
it('should set focus back to the search box when the search results are displayed and the escape key is pressed', () => {
const searchBox: SearchBoxComponent = fixture.debugElement.query(By.directive(SearchBoxComponent)).componentInstance;
const searchBox: SearchBoxComponent = fixture.debugElement.query(
By.directive(SearchBoxComponent)
).componentInstance;
spyOn(searchBox, 'focus');
component.showSearchResults = true;
window.document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
@@ -809,7 +823,15 @@ describe('AppComponent', () => {
const searchService = TestBed.inject(SearchService) as Partial<SearchService> as MockSearchService;
const results = [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' }
{
path: 'news',
title: 'News',
type: 'marketing',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
];
searchService.searchResults.next({ query: 'something', results });
+10 -1
View File
@@ -1,4 +1,13 @@
import { Component, ElementRef, HostBinding, HostListener, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import {
Component,
ElementRef,
HostBinding,
HostListener,
OnInit,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { DocumentContents, DocumentService } from 'app/documents/document.service';
import { NotificationComponent } from 'app/layout/notification/notification.component';
@@ -11,20 +11,22 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
<div class="card-front" (click)="flipCard(person)" (keyup.enter)="flipCard(person)">
<h3>{{person.name}}</h3>
<div class="contributor-image" [style.background-image]="'url('+pictureBase+(person.picture || noPicture)+')'">
<div class="contributor-info">
<a *ngIf="person.bio" mat-button class="info-item">
View Bio
</a>
<a *ngIf="person.twitter" mat-icon-button class="info-item icon"
href="https://twitter.com/{{person.twitter}}" target="_blank" (click)="$event.stopPropagation()">
<mat-icon svgIcon="logos:twitter"></mat-icon>
</a>
<a *ngIf="person.website" mat-icon-button class="info-item icon"
href="{{person.website}}" target="_blank" (click)="$event.stopPropagation()">
<mat-icon class="link-icon">link</mat-icon>
</a>
</div>
<div class="contributor-image"
[style.background-image]="'url('+pictureBase+(person.picture || noPicture)+')'">
<div class="contributor-info">
<a *ngIf="person.bio" mat-button class="info-item">
View Bio
</a>
<a *ngIf="person.twitter" mat-icon-button class="info-item icon"
href="https://twitter.com/{{person.twitter}}"
target="_blank" (click)="$event.stopPropagation()">
<mat-icon svgIcon="logos:twitter"></mat-icon>
</a>
<a *ngIf="person.website" mat-icon-button class="info-item icon"
href="{{person.website}}" target="_blank" (click)="$event.stopPropagation()">
<mat-icon class="link-icon">link</mat-icon>
</a>
</div>
</div>
</div>
@@ -60,7 +60,9 @@ export interface WithCustomElementComponent {
}
/** Injection token to provide the element path modules. */
export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<Map<string, LoadChildrenCallback>>('aio/elements-map');
export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<
Map<string, LoadChildrenCallback>
>('aio/elements-map');
/** Map of possible custom element selectors to their lazy-loadable module paths. */
export const ELEMENT_MODULE_LOAD_CALLBACKS = new Map<string, LoadChildrenCallback>();
@@ -52,7 +52,7 @@ export class ElementsLoader {
// Load and register the custom element (for the first time).
const modulePathLoader = this.elementsToLoad.get(selector) as LoadChildrenCallback;
const loadedAndRegistered =
(modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>)
(modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>)
.then(elementModuleOrFactory => {
/**
* With View Engine, the NgModule factory is created and provided when loaded.
@@ -110,7 +110,9 @@ describe('TocComponent', () => {
describe('when fewer than `maxPrimary` TocItems', () => {
beforeEach(() => {
tocService.tocList.next([tocItem('Heading A'), tocItem('Heading B'), tocItem('Heading C'), tocItem('Heading D')]);
tocService.tocList.next(
[ tocItem('Heading A'), tocItem('Heading B'), tocItem('Heading C'), tocItem('Heading D') ]
);
fixture.detectChanges();
page = setPage();
});
@@ -51,7 +51,8 @@ export class TocComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.isEmbedded) {
// We use the `asap` scheduler because updates to `activeItemIndex` are triggered by DOM changes,
// which, in turn, are caused by the rendering that happened due to a ChangeDetection.
// Without asap, we would be updating the model while still in a ChangeDetection handler, which is disallowed by Angular.
// Without asap, we would be updating the model while still in a ChangeDetection handler,
// which is disallowed by Angular.
combineLatest([
this.tocService.activeItemIndex.pipe(subscribeOn(asapScheduler)),
this.items.changes.pipe(startWith(this.items)),
@@ -135,12 +135,17 @@ describe('DocumentService', () => {
httpMock.expectOne({}).flush(null, {status: 500, statusText: 'Server Error'});
expect(latestDocument.id).toBe(FETCHING_ERROR_ID);
expect(latestDocument.contents?.toString()).toContain('We are unable to retrieve the "initial/doc" page at this time.');
expect(
latestDocument.contents?.toString()
).toContain('We are unable to retrieve the "initial/doc" page at this time.');
expect(logger.output.error).toEqual([
[jasmine.any(Error)]
]);
expect(logger.output.error[0][0].message)
.toEqual("Error fetching document 'initial/doc': (Http failure response for generated/docs/initial/doc.json: 500 Server Error)");
.toEqual(
'Error fetching document \'initial/doc\': ' +
'(Http failure response for generated/docs/initial/doc.json: 500 Server Error)'
);
locationService.go('new/doc');
httpMock.expectOne({}).flush(doc1);
@@ -212,8 +212,12 @@ export class DocViewerComponent implements OnDestroy {
// Some properties are not assignable and thus cannot be animated.
// Example methods, readonly and CSS properties:
// "length", "parentRule", "getPropertyPriority", "getPropertyValue", "item", "removeProperty", "setProperty"
type StringValueCSSStyleDeclaration
= Exclude<{ [K in keyof CSSStyleDeclaration]: CSSStyleDeclaration[K] extends string ? K : never }[keyof CSSStyleDeclaration], number>;
type StringValueCSSStyleDeclaration = Exclude<
{
[K in keyof CSSStyleDeclaration]: CSSStyleDeclaration[K] extends string ? K : never;
}[keyof CSSStyleDeclaration],
number
>;
const animateProp =
(elem: HTMLElement, prop: StringValueCSSStyleDeclaration, from: string, to: string, duration = 200) => {
const animationsDisabled = !DocViewerComponent.animationsEnabled
@@ -4,11 +4,13 @@ import { VersionInfo } from 'app/navigation/navigation.service';
@Component({
selector: 'aio-mode-banner',
template: `
<div *ngIf="mode === 'archive'" class="mode-banner alert archive-warning">
<p>This is the <strong>archived documentation for Angular v{{version?.major}}.</strong>
Please visit <a href="https://angular.io/">angular.io</a> to see documentation for the current version of Angular.</p>
</div>
`
<div *ngIf="mode === 'archive'" class="mode-banner alert archive-warning">
<p>
This is the <strong>archived documentation for Angular v{{ version?.major }}.</strong> Please visit
<a href="https://angular.io/">angular.io</a> to see documentation for the current version of Angular.
</p>
</div>
`,
})
export class ModeBannerComponent {
@Input() mode: string;
+10 -3
View File
@@ -9,7 +9,14 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
// Import and re-export the Navigation model types
import { CurrentNodes, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
export { CurrentNodes, CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
export {
CurrentNodes,
CurrentNode,
NavigationNode,
NavigationResponse,
NavigationViews,
VersionInfo,
} from './navigation.model';
export const navigationPath = CONTENT_URL_PREFIX + 'navigation.json';
@@ -46,8 +53,8 @@ export class NavigationService {
* We create an observable by calling `http.get` but then publish it to share the result
* among multiple subscribers, without triggering new requests.
* We use `publishLast` because once the http request is complete the request observable completes.
* If you use `publish` here then the completed request observable will cause the subscribed observables to complete too.
* We `connect` to the published observable to trigger the request immediately.
* If you use `publish` here then the completed request observable will cause the subscribed
* observables to complete too. We `connect` to the published observable to trigger the request immediately.
* We could use `.refCount` here but then if the subscribers went from 1 -> 0 -> 1 then you would get
* another request to the server.
* We are not storing the subscription from connecting as we do not expect this service to be destroyed.
+3 -1
View File
@@ -389,7 +389,9 @@ describe('LocationService', () => {
it('should convert the params to a query string', () => {
const params = { foo: 'bar', moo: 'car' };
service.setSearch('Some label', params);
expect(platformLocation.replaceState).toHaveBeenCalledWith(jasmine.any(Object), 'Some label', jasmine.any(String));
expect(platformLocation.replaceState).toHaveBeenCalledWith(
jasmine.any(Object),'Some label', jasmine.any(String)
);
const [path, query] = platformLocation.replaceState.calls.mostRecent().args[2].split('?');
expect(path).toEqual('a/b/c');
expect(query).toContain('foo=bar');
@@ -3,7 +3,12 @@ import { fakeAsync, tick } from '@angular/core/testing';
import { DOCUMENT } from '@angular/common';
import { ScrollService } from 'app/shared/scroll.service';
import { ScrollItem, ScrollSpiedElement, ScrollSpiedElementGroup, ScrollSpyService } from 'app/shared/scroll-spy.service';
import {
ScrollItem,
ScrollSpiedElement,
ScrollSpiedElementGroup,
ScrollSpyService,
} from 'app/shared/scroll-spy.service';
describe('ScrollSpiedElement', () => {
@@ -37,6 +37,7 @@ describe('SearchResultsComponent', () => {
/** Get a full set of test results. "Take" what you need */
beforeEach(() => {
/* eslint-disable max-len */
apiD = { path: 'api/d', title: 'API D', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
apiC = { path: 'api/c', title: 'API C', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideA = { path: 'guide/a', title: 'Guide A', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
@@ -52,9 +53,24 @@ describe('SearchResultsComponent', () => {
guideL = { path: 'guide/l', title: 'Guide l', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideM = { path: 'guide/m', title: 'Guide m', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
guideN = { path: 'guide/n', title: 'Guide n', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
/* eslint-enable max-len */
standardResults = [
guideA, apiD, guideB, guideAC, apiC, guideN, guideM, guideL, guideK, guideJ, guideI, guideH, guideG, guideF, guideE,
guideA,
apiD,
guideB,
guideAC,
apiC,
guideN,
guideM,
guideL,
guideK,
guideJ,
guideI,
guideH,
guideG,
guideF,
guideE,
];
});
@@ -71,8 +87,24 @@ describe('SearchResultsComponent', () => {
});
it('should map the search results into groups based on their containing folder', () => {
const startA = { path: 'start/a', title: 'Start A', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
const tutorialA = { path: 'tutorial/a', title: 'Tutorial A', deprecated: false, keywords: '', titleWords: '', type: '', topics: '' };
const startA = {
path: 'start/a',
title: 'Start A',
deprecated: false,
keywords: '',
titleWords: '',
type: '',
topics: '',
};
const tutorialA = {
path: 'tutorial/a',
title: 'Tutorial A',
deprecated: false,
keywords: '',
titleWords: '',
type: '',
topics: '',
};
setSearchResults('', [guideA, apiD, guideB, startA, tutorialA]);
expect(component.searchAreas).toEqual([
@@ -84,20 +116,92 @@ describe('SearchResultsComponent', () => {
it('should special case results that are top level folders', () => {
setSearchResults('', [
{ path: 'docs', title: 'Docs introduction', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'start', title: 'Getting started', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{
path: 'docs',
title: 'Docs introduction',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
{
path: 'start',
title: 'Getting started',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
{
path: 'tutorial',
title: 'Tutorial index',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
{
path: 'tutorial/toh-pt1',
title: 'Tutorial - part 1',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
]);
expect(component.searchAreas).toEqual([
{ name: 'guides', priorityPages: [
{ path: 'docs', title: 'Docs introduction', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
], pages: [] },
{ name: 'tutorials', priorityPages: [
{ path: 'start', title: 'Getting started', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false, topics: '' },
], pages: [] },
{
name: 'guides',
priorityPages: [
{
path: 'docs',
title: 'Docs introduction',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
],
pages: [],
},
{
name: 'tutorials',
priorityPages: [
{
path: 'start',
title: 'Getting started',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
{
path: 'tutorial',
title: 'Tutorial index',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
{
path: 'tutorial/toh-pt1',
title: 'Tutorial - part 1',
type: '',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
},
],
pages: [],
},
]);
});
@@ -154,6 +258,7 @@ describe('SearchResultsComponent', () => {
guideE.deprecated = true;
setSearchResults('something', standardResults);
});
// eslint-disable-next-line max-len
it('should include deprecated items in priority pages unless there are fewer than 5 non-deprecated priority pages', () => {
// Priority pages do not include deprecated items:
expect(component.searchAreas[1].priorityPages).not.toContain(guideAC);
@@ -195,7 +300,15 @@ describe('SearchResultsComponent', () => {
component.resultSelected.subscribe((result: SearchResult) => selected = result);
selected = null;
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false, topics: '' };
searchResult = {
path: 'news',
title: 'News',
type: 'marketing',
keywords: '',
titleWords: '',
deprecated: false,
topics: '',
};
setSearchResults('something', [searchResult]);
fixture.detectChanges();
+8 -2
View File
@@ -130,8 +130,14 @@ export class TocService {
}
// Helpers
function querySelectorAll<K extends keyof HTMLElementTagNameMap>(parent: Element, selector: K): HTMLElementTagNameMap[K][];
function querySelectorAll<K extends keyof SVGElementTagNameMap>(parent: Element, selector: K): SVGElementTagNameMap[K][];
function querySelectorAll<K extends keyof HTMLElementTagNameMap>(
parent: Element,
selector: K
): HTMLElementTagNameMap[K][];
function querySelectorAll<K extends keyof SVGElementTagNameMap>(
parent: Element,
selector: K
): SVGElementTagNameMap[K][];
function querySelectorAll<E extends Element = Element>(parent: Element, selector: string): E[];
function querySelectorAll(parent: Element, selector: string) {
// Wrap the `NodeList` as a regular `Array` to have access to array methods.
+1 -1
View File
@@ -1,7 +1,7 @@
import { browser } from 'protractor';
import { SitePage } from './app.po';
/* tslint:disable:max-line-length */
/* eslint-disable max-len */
describe('onerror handler', () => {
let page: SitePage;
@@ -1,4 +1,5 @@
{
"root": true,
"extends": "../../.eslintrc.json",
"overrides": [
{
@@ -20,8 +20,14 @@ export class FirebaseRedirect {
return undefined;
}
const namedReplacers = this.source.namedGroups.map<[RegExp, string]>(name => [ XRegExp(`:${name}`, 'g'), match[name] ]);
const restReplacers = this.source.restNamedGroups.map<[RegExp, string]>(name => [ XRegExp(`:${name}\\*`, 'g'), match[name] ]);
const namedReplacers = this.source.namedGroups.map<[RegExp, string]>((name) => [
XRegExp(`:${name}`, 'g'),
match[name],
]);
const restReplacers = this.source.restNamedGroups.map<[RegExp, string]>((name) => [
XRegExp(`:${name}\\*`, 'g'),
match[name],
]);
return XRegExp.replaceEach(this.destination, [...namedReplacers, ...restReplacers]);
}
}
@@ -1,4 +1,5 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:jasmine/recommended',
+1
View File
@@ -1,4 +1,5 @@
module.exports = {
'root': true,
'env': {
'es6': true,
'jasmine': true,