Skip to content

Commit 9f51fab

Browse files
authored
Fix "implicit any" Typescript errors to prepare for Typescript 5.5 (#1817)
* Teach Typescript about the types of field keys Typescript 5.5 disallows the "suppressImplicitAnyIndexErrors", so we need to go through the code, find any places where an index has an implicit "any" type, and annotate those places with the correct type so that Typescript won't complain about them. In a couple places, we do `if foo.hasOwnProperty(bar)` followed by getting the value of `foo[bar]`, but Typescript warns that "string" isn't a valid property name of `foo`. It should recognize that the hasOwnProperty check means it's safe to access `foo[bar]`, but it doesn't. We can either change those to be `if bar in foo` which Typescript *does* recognize as a type guard, or else we can just leave the code as-is and add @ts-expect-error comments. Since `bar in foo` is not quite identical to `foo.hasOwnProperty(bar)` (`bar in foo` climbs inheritance hierarchies whereas `.hasOwnProperty` doesn't), I chose to leave the code as-is rather than subtly change its behavior. * Remove suppressImplicitAnyIndexErrors from tsconfig Now that we've fixed the implicit "any" index errors, we can remove this line from tsconfig. (It will be going away in Typescript 5.5 anyway).
1 parent 7867825 commit 9f51fab

File tree

11 files changed

+45
-39
lines changed

11 files changed

+45
-39
lines changed

src/angular-app/bellows/apps/activity/activity-container.component.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ class Activity {
4242
userHrefRelated: string;
4343
icon: string;
4444

45-
constructor(data: object = {}) {
45+
constructor(data: Record<string, any> = {}) {
4646
if (data != null) {
4747
for (const property of Object.keys(data)) {
48-
this[property] = data[property];
48+
this[property as keyof Activity] = data[property];
4949
}
5050
}
5151
}
@@ -57,6 +57,7 @@ class Activity {
5757
for (const index in this.content) {
5858
if (this.content.hasOwnProperty(index)) {
5959
if (index.startsWith('oldValue')) {
60+
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
6061
return Activity.parseValue(this.content[index]);
6162
}
6263
}
@@ -72,6 +73,7 @@ class Activity {
7273
for (const index in this.content) {
7374
if (this.content.hasOwnProperty(index)) {
7475
if (index.startsWith('newValue')) {
76+
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
7577
return Activity.parseValue(this.content[index]);
7678
}
7779
}
@@ -89,6 +91,7 @@ class Activity {
8991
for (const index in this.content) {
9092
if (this.content.hasOwnProperty(index)) {
9193
if (index.startsWith('fieldLabel')) {
94+
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
9295
let label = this.content[index];
9396
if (index.includes('#examples')) {
9497
label = 'Example - ' + label;
@@ -164,8 +167,8 @@ class ActivityUserGroup {
164167
getSummaryDescription(entryId: string) {
165168
let summary = '';
166169
let totalActivityTypes = 0;
167-
const entryActivities = {};
168-
const summaryTypes = {};
170+
const entryActivities: Record<string, any> = {};
171+
const summaryTypes: Record<string, any> = {};
169172
for (const activity of this.activities) {
170173
// Entries are different as multiple updates can be reflected in a single activity
171174
if (activity.action === 'update_entry') {

src/angular-app/bellows/core/breadcrumbs/breadcrumb.service.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@ export class BreadcrumbService {
1515
updateCrumb(id: string, index: number, update: Crumb): void {
1616
this.ensureIdIsRegistered(id);
1717
const crumb = this.crumbStore[id][index];
18-
for (const property in update) {
19-
if (update.hasOwnProperty(property)) {
20-
crumb[property] = update[property];
21-
}
22-
}
18+
Object.assign(crumb, update);
2319
}
2420

2521
set(id: string, crumbs: Crumb[]): void {

src/angular-app/bellows/core/offline/editor-data.service.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {SemanticDomainsService} from '../../../languageforge/core/semantic-domai
44
import {LexiconConfigService} from '../../../languageforge/lexicon/core/lexicon-config.service';
55
import {LexiconUtilityService} from '../../../languageforge/lexicon/core/lexicon-utility.service';
66
import {LexEntry} from '../../../languageforge/lexicon/shared/model/lex-entry.model';
7+
import {LexSense} from 'src/angular-app/languageforge/lexicon/shared/model/lex-sense.model';
8+
import {LexExample} from 'src/angular-app/languageforge/lexicon/shared/model/lex-example.model';
9+
import {LexMultiText} from 'src/angular-app/languageforge/lexicon/shared/model/lex-multi-text.model';
710
import {LexMultiValue} from '../../../languageforge/lexicon/shared/model/lex-multi-value.model';
11+
import {LexOptionListItem} from 'src/angular-app/languageforge/lexicon/shared/model/option-list.model';
812
import {
913
LexConfigFieldList,
1014
LexConfigMultiOptionList,
@@ -534,7 +538,7 @@ export class EditorDataService {
534538
field = config.entry.fields.senses.fields[fieldKey];
535539
}
536540

537-
angular.forEach(config.entry.fields, (entryField, entryFieldKey:string) => {
541+
angular.forEach(config.entry.fields, (entryField, entryFieldKey:keyof LexEntry) => {
538542
if (entryField.type === 'multitext') {
539543
angular.forEach(entry[entryFieldKey], (fieldNode, ws:string) => {
540544
if (ws && UtilityService.isAudio(ws) && fieldNode.value !== '') {
@@ -545,7 +549,7 @@ export class EditorDataService {
545549

546550
if (entryFieldKey === 'senses') {
547551
angular.forEach(entry.senses, sense => {
548-
angular.forEach(config.entry.fields.senses.fields, (senseField: any, senseFieldKey: string) => {
552+
angular.forEach(config.entry.fields.senses.fields, (senseField: any, senseFieldKey: keyof LexSense) => {
549553
if (senseField.type === 'multitext') {
550554
angular.forEach(sense[senseFieldKey], (fieldNode: any, ws: string) => {
551555
if (ws && UtilityService.isAudio(ws) && fieldNode.value !== '') {
@@ -557,7 +561,7 @@ export class EditorDataService {
557561
if (senseFieldKey === 'examples') {
558562
angular.forEach(sense.examples, example => {
559563
angular.forEach(config.entry.fields.senses.fields.examples.fields,
560-
(exampleField: any, exampleFieldKey: string) => {
564+
(exampleField: any, exampleFieldKey: keyof LexExample) => {
561565
if (exampleField.type === 'multitext') {
562566
angular.forEach(example[exampleFieldKey], (fieldNode: any, ws: string) => {
563567
if (ws && UtilityService.isAudio(ws) && fieldNode.value !== '') {
@@ -579,25 +583,25 @@ export class EditorDataService {
579583
// filter by entry or sense field
580584
let dataNode;
581585
if (this.entryListModifiers.filterBy.option.level === 'entry') {
582-
dataNode = entry[this.entryListModifiers.filterBy.option.value];
586+
dataNode = entry[this.entryListModifiers.filterBy.option.value as keyof LexEntry];
583587
} else { // sense level
584588
if (entry.senses && entry.senses.length > 0) {
585-
dataNode = entry.senses[0][this.entryListModifiers.filterBy.option.value];
589+
dataNode = entry.senses[0][this.entryListModifiers.filterBy.option.value as keyof LexSense];
586590
}
587591
}
588592

589593
if (dataNode) {
590594
switch (filterType) {
591595
case 'multitext':
592-
if (dataNode[this.entryListModifiers.filterBy.option.inputSystem]) {
593-
containsData = dataNode[this.entryListModifiers.filterBy.option.inputSystem].value !== '';
596+
if ((dataNode as LexMultiText)[this.entryListModifiers.filterBy.option.inputSystem]) {
597+
containsData = (dataNode as LexMultiText)[this.entryListModifiers.filterBy.option.inputSystem].value !== '';
594598
}
595599
break;
596600
case 'optionlist':
597-
containsData = dataNode.value !== '';
601+
containsData = (dataNode as LexOptionListItem).value !== '';
598602
break;
599603
case 'multioptionlist':
600-
containsData = (dataNode.values.length > 0);
604+
containsData = ((dataNode as LexMultiValue).values.length > 0);
601605
break;
602606
}
603607
}

src/angular-app/bellows/core/session.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class Session {
4949
return rights.indexOf(right) !== -1;
5050
}
5151

52-
getProjectSetting(setting: string) {
52+
getProjectSetting(setting: keyof ProjectSettings) {
5353
return this.data.projectSettings[setting];
5454
}
5555
}

src/angular-app/bellows/shared/tabset.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class TabSetController implements angular.IController {
3737
this.tabs[index].onSelect();
3838
this.$scope.active = index;
3939
for (const i of Object.keys(this.tabs)) {
40-
this.tabs[i].selected = (parseInt(i, 10) === index);
40+
this.tabs[+i].selected = (+i === index);
4141
}
4242
}
4343

src/angular-app/languageforge/lexicon/editor/comment/lex-comments-view.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ export class LexCommentsViewController implements angular.IController {
7474
}
7575

7676
// get value of multi-text with specified inputSystemTag
77-
if (inputSystemTag != null && model[inputSystemTag] != null) {
78-
return model[inputSystemTag].value;
77+
if (inputSystemTag != null && (model as LexMultiText)[inputSystemTag] != null) {
78+
return (model as LexMultiText)[inputSystemTag].value;
7979
}
8080

8181
// get first inputSystemTag of a multi-text (no inputSystemTag specified)
8282
let fieldValue: string = null;
8383
for (const languageTag in model as LexMultiText) {
8484
if (fieldValue == null) {
85-
fieldValue = model[languageTag].value;
85+
fieldValue = (model as LexMultiText)[languageTag].value;
8686
break;
8787
}
8888
}

src/angular-app/languageforge/lexicon/editor/editor.component.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import { LexiconRightsService, Rights } from '../core/lexicon-rights.service';
2121
import { LexiconSendReceiveService } from '../core/lexicon-send-receive.service';
2222
import { LexiconUtilityService } from '../core/lexicon-utility.service';
2323
import { LexEntry } from '../shared/model/lex-entry.model';
24+
import { LexSense } from '../shared/model/lex-sense.model';
25+
import { LexExample } from '../shared/model/lex-example.model';
2426
import { LexPicture } from '../shared/model/lex-picture.model';
2527
import {
2628
LexConfig,
@@ -867,13 +869,13 @@ export class LexiconEditorController implements angular.IController {
867869
if (senseGuid) {
868870
for (const a in currentEntry.senses) {
869871
if (currentEntry.senses.hasOwnProperty(a) && currentEntry.senses[a].guid === senseGuid) {
870-
senseIndex = a;
872+
senseIndex = +a;
871873
if (exampleGuid) {
872874
for (const b in currentEntry.senses[a].examples) {
873875
if (currentEntry.senses[a].examples.hasOwnProperty(b) &&
874876
currentEntry.senses[a].examples[b].guid === exampleGuid
875877
) {
876-
exampleIndex = b;
878+
exampleIndex = +b;
877879
}
878880
}
879881
}
@@ -885,29 +887,31 @@ export class LexiconEditorController implements angular.IController {
885887
const examples = senses.fields.examples as LexConfigFieldList;
886888
if (exampleGuid && exampleIndex) {
887889
if (currentEntry.senses[senseIndex].examples[exampleIndex].hasOwnProperty(field)) {
888-
currentField = currentEntry.senses[senseIndex].examples[exampleIndex][field];
890+
currentField = currentEntry.senses[senseIndex].examples[exampleIndex][field as keyof LexExample];
889891
if (examples.fields.hasOwnProperty(field)) {
890892
fieldConfig = examples.fields[field];
891893
}
892894
}
893895
} else if (senseGuid && senseIndex) {
894896
if (currentEntry.senses[senseIndex].hasOwnProperty(field)) {
895-
currentField = currentEntry.senses[senseIndex][field];
897+
currentField = currentEntry.senses[senseIndex][field as keyof LexSense];
896898
if (senses.fields.hasOwnProperty(field)) {
897899
fieldConfig = senses.fields[field];
898900
}
899901
}
900902
} else if (currentEntry.hasOwnProperty(field)) {
901-
currentField = currentEntry[field];
903+
currentField = currentEntry[field as keyof LexEntry];
902904
if (this.lecConfig.entry.fields.hasOwnProperty(field)) {
903905
fieldConfig = this.lecConfig.entry.fields[field];
904906
}
905907
}
906908

907909
if (currentField !== null) {
908910
if (currentField.hasOwnProperty(inputSystem)) {
911+
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
909912
currentValue = currentField[inputSystem].value;
910913
} else if (currentField.hasOwnProperty('value')) {
914+
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
911915
currentValue = currentField.value;
912916
} else {
913917
currentValue = optionKey;
@@ -956,9 +960,9 @@ export class LexiconEditorController implements angular.IController {
956960
parts.field = field;
957961
parts.fieldConfig = fieldConfig;
958962
parts.inputSystem = inputSystem;
959-
parts.sense.index = senseIndex;
963+
parts.sense.index = ""+senseIndex;
960964
parts.sense.guid = senseGuid;
961-
parts.example.index = exampleIndex;
965+
parts.example.index = ""+exampleIndex;
962966
parts.example.guid = exampleGuid;
963967
return parts;
964968
}

src/angular-app/languageforge/lexicon/editor/field/dc-text.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class FieldTextController implements angular.IController {
5050
this.fte.toolbar = '[[]]';
5151
}
5252

53-
this.autocapitalize = autocapitalizeHints[this.fteFieldName] || 'none'
53+
this.autocapitalize = autocapitalizeHints[this.fteFieldName as keyof typeof autocapitalizeHints] || 'none'
5454
}
5555

5656
disabledMsg(): string {

src/angular-app/languageforge/lexicon/lexicon-app.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class LexiconAppController implements angular.IController {
5757
if (rights.canEditProject()) {
5858
this.lexProjectService.users().then(result => {
5959
if (result.ok) {
60-
const users = {};
60+
const users: Record<string, User> = {};
6161
for (const user of (result.data.users as User[])) {
6262
users[user.id] = user;
6363
}

src/angular-app/languageforge/lexicon/settings/configuration/field-unified-view.model.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export class ConfigurationFieldUnifiedViewModel {
136136
}
137137
}
138138

139-
static selectAllRoleColumn(settings: SettingsBase[], selectAll: SettingsBase, role: string): void {
139+
static selectAllRoleColumn(settings: SettingsBase[], selectAll: SettingsBase, role: RoleName): void {
140140
for (const setting of settings) {
141141
if (RequiredFields.requiredFieldList.includes(setting.fieldName)) {
142142
setting[role] = true;
@@ -158,7 +158,7 @@ export class ConfigurationFieldUnifiedViewModel {
158158
}
159159
}
160160

161-
static checkIfAllRoleColumnSelected(settings: SettingsBase[], selectAll: SettingsBase, role: string): void {
161+
static checkIfAllRoleColumnSelected(settings: SettingsBase[], selectAll: SettingsBase, role: RoleName): void {
162162
selectAll[role] = true;
163163
for (const setting of settings) {
164164
if (!setting[role]) {
@@ -180,7 +180,7 @@ export class ConfigurationFieldUnifiedViewModel {
180180
}
181181

182182
static checkIfAllRoleSelected(setting: SettingsBase, settings: SettingsBase[], selectAll: SettingsBase,
183-
role: string): void {
183+
role: RoleName): void {
184184
ConfigurationFieldUnifiedViewModel.checkIfAllRowSelected(setting);
185185
ConfigurationFieldUnifiedViewModel.checkIfAllRoleColumnSelected(settings, selectAll, role);
186186
}
@@ -405,7 +405,7 @@ export class ConfigurationFieldUnifiedViewModel {
405405
return tags;
406406
}
407407

408-
private static setInputSystemRoleSettings(tag: string, config: LexiconConfig, role: string, roleType: string,
408+
private static setInputSystemRoleSettings(tag: string, config: LexiconConfig, role: RoleName, roleType: string,
409409
inputSystemSettings: InputSystemSettings): void {
410410
const roleView: LexRoleViewConfig = config.roleViews[roleType];
411411
if (roleView != null) {
@@ -546,13 +546,14 @@ export class FieldSettingsList {
546546
selectAllColumns: FieldSettings = new FieldSettings();
547547
}
548548

549+
export type RoleName = 'observer' | 'commenter' | 'contributor' | 'manager';
549550
export class RoleType {
550551
observer: string = 'observer';
551552
commenter: string = 'observer_with_comment';
552553
contributor: string = 'contributor';
553554
manager: string = 'project_manager';
554555

555-
static roles(): string[] {
556+
static roles(): RoleName[] {
556557
return ['observer', 'commenter', 'contributor', 'manager'];
557558
}
558559
}

tsconfig.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
"downlevelIteration": true,
1515
"emitDecoratorMetadata": true,
1616
"experimentalDecorators": true,
17-
"ignoreDeprecations": "5.0",
1817
"importHelpers": true,
1918
"lib": ["es7", "dom"],
2019
"module": "es6",
@@ -26,7 +25,6 @@
2625
},
2726
"rootDir": "src/angular-app",
2827
"sourceMap": true,
29-
"suppressImplicitAnyIndexErrors": true,
3028
"target": "es5",
3129
"typeRoots": ["node_modules/@types", "typings"]
3230
},

0 commit comments

Comments
 (0)