Skip to content

Commit

Permalink
Fix "implicit any" Typescript errors to prepare for Typescript 5.5 (#…
Browse files Browse the repository at this point in the history
…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).
  • Loading branch information
rmunn authored May 27, 2024
1 parent 7867825 commit 9f51fab
Show file tree
Hide file tree
Showing 11 changed files with 45 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ class Activity {
userHrefRelated: string;
icon: string;

constructor(data: object = {}) {
constructor(data: Record<string, any> = {}) {
if (data != null) {
for (const property of Object.keys(data)) {
this[property] = data[property];
this[property as keyof Activity] = data[property];
}
}
}
Expand All @@ -57,6 +57,7 @@ class Activity {
for (const index in this.content) {
if (this.content.hasOwnProperty(index)) {
if (index.startsWith('oldValue')) {
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
return Activity.parseValue(this.content[index]);
}
}
Expand All @@ -72,6 +73,7 @@ class Activity {
for (const index in this.content) {
if (this.content.hasOwnProperty(index)) {
if (index.startsWith('newValue')) {
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
return Activity.parseValue(this.content[index]);
}
}
Expand All @@ -89,6 +91,7 @@ class Activity {
for (const index in this.content) {
if (this.content.hasOwnProperty(index)) {
if (index.startsWith('fieldLabel')) {
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
let label = this.content[index];
if (index.includes('#examples')) {
label = 'Example - ' + label;
Expand Down Expand Up @@ -164,8 +167,8 @@ class ActivityUserGroup {
getSummaryDescription(entryId: string) {
let summary = '';
let totalActivityTypes = 0;
const entryActivities = {};
const summaryTypes = {};
const entryActivities: Record<string, any> = {};
const summaryTypes: Record<string, any> = {};
for (const activity of this.activities) {
// Entries are different as multiple updates can be reflected in a single activity
if (activity.action === 'update_entry') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ export class BreadcrumbService {
updateCrumb(id: string, index: number, update: Crumb): void {
this.ensureIdIsRegistered(id);
const crumb = this.crumbStore[id][index];
for (const property in update) {
if (update.hasOwnProperty(property)) {
crumb[property] = update[property];
}
}
Object.assign(crumb, update);
}

set(id: string, crumbs: Crumb[]): void {
Expand Down
22 changes: 13 additions & 9 deletions src/angular-app/bellows/core/offline/editor-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {SemanticDomainsService} from '../../../languageforge/core/semantic-domai
import {LexiconConfigService} from '../../../languageforge/lexicon/core/lexicon-config.service';
import {LexiconUtilityService} from '../../../languageforge/lexicon/core/lexicon-utility.service';
import {LexEntry} from '../../../languageforge/lexicon/shared/model/lex-entry.model';
import {LexSense} from 'src/angular-app/languageforge/lexicon/shared/model/lex-sense.model';
import {LexExample} from 'src/angular-app/languageforge/lexicon/shared/model/lex-example.model';
import {LexMultiText} from 'src/angular-app/languageforge/lexicon/shared/model/lex-multi-text.model';
import {LexMultiValue} from '../../../languageforge/lexicon/shared/model/lex-multi-value.model';
import {LexOptionListItem} from 'src/angular-app/languageforge/lexicon/shared/model/option-list.model';
import {
LexConfigFieldList,
LexConfigMultiOptionList,
Expand Down Expand Up @@ -534,7 +538,7 @@ export class EditorDataService {
field = config.entry.fields.senses.fields[fieldKey];
}

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

if (entryFieldKey === 'senses') {
angular.forEach(entry.senses, sense => {
angular.forEach(config.entry.fields.senses.fields, (senseField: any, senseFieldKey: string) => {
angular.forEach(config.entry.fields.senses.fields, (senseField: any, senseFieldKey: keyof LexSense) => {
if (senseField.type === 'multitext') {
angular.forEach(sense[senseFieldKey], (fieldNode: any, ws: string) => {
if (ws && UtilityService.isAudio(ws) && fieldNode.value !== '') {
Expand All @@ -557,7 +561,7 @@ export class EditorDataService {
if (senseFieldKey === 'examples') {
angular.forEach(sense.examples, example => {
angular.forEach(config.entry.fields.senses.fields.examples.fields,
(exampleField: any, exampleFieldKey: string) => {
(exampleField: any, exampleFieldKey: keyof LexExample) => {
if (exampleField.type === 'multitext') {
angular.forEach(example[exampleFieldKey], (fieldNode: any, ws: string) => {
if (ws && UtilityService.isAudio(ws) && fieldNode.value !== '') {
Expand All @@ -579,25 +583,25 @@ export class EditorDataService {
// filter by entry or sense field
let dataNode;
if (this.entryListModifiers.filterBy.option.level === 'entry') {
dataNode = entry[this.entryListModifiers.filterBy.option.value];
dataNode = entry[this.entryListModifiers.filterBy.option.value as keyof LexEntry];
} else { // sense level
if (entry.senses && entry.senses.length > 0) {
dataNode = entry.senses[0][this.entryListModifiers.filterBy.option.value];
dataNode = entry.senses[0][this.entryListModifiers.filterBy.option.value as keyof LexSense];
}
}

if (dataNode) {
switch (filterType) {
case 'multitext':
if (dataNode[this.entryListModifiers.filterBy.option.inputSystem]) {
containsData = dataNode[this.entryListModifiers.filterBy.option.inputSystem].value !== '';
if ((dataNode as LexMultiText)[this.entryListModifiers.filterBy.option.inputSystem]) {
containsData = (dataNode as LexMultiText)[this.entryListModifiers.filterBy.option.inputSystem].value !== '';
}
break;
case 'optionlist':
containsData = dataNode.value !== '';
containsData = (dataNode as LexOptionListItem).value !== '';
break;
case 'multioptionlist':
containsData = (dataNode.values.length > 0);
containsData = ((dataNode as LexMultiValue).values.length > 0);
break;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/angular-app/bellows/core/session.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class Session {
return rights.indexOf(right) !== -1;
}

getProjectSetting(setting: string) {
getProjectSetting(setting: keyof ProjectSettings) {
return this.data.projectSettings[setting];
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/angular-app/bellows/shared/tabset.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class TabSetController implements angular.IController {
this.tabs[index].onSelect();
this.$scope.active = index;
for (const i of Object.keys(this.tabs)) {
this.tabs[i].selected = (parseInt(i, 10) === index);
this.tabs[+i].selected = (+i === index);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ export class LexCommentsViewController implements angular.IController {
}

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

// get first inputSystemTag of a multi-text (no inputSystemTag specified)
let fieldValue: string = null;
for (const languageTag in model as LexMultiText) {
if (fieldValue == null) {
fieldValue = model[languageTag].value;
fieldValue = (model as LexMultiText)[languageTag].value;
break;
}
}
Expand Down
18 changes: 11 additions & 7 deletions src/angular-app/languageforge/lexicon/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import { LexiconRightsService, Rights } from '../core/lexicon-rights.service';
import { LexiconSendReceiveService } from '../core/lexicon-send-receive.service';
import { LexiconUtilityService } from '../core/lexicon-utility.service';
import { LexEntry } from '../shared/model/lex-entry.model';
import { LexSense } from '../shared/model/lex-sense.model';
import { LexExample } from '../shared/model/lex-example.model';
import { LexPicture } from '../shared/model/lex-picture.model';
import {
LexConfig,
Expand Down Expand Up @@ -867,13 +869,13 @@ export class LexiconEditorController implements angular.IController {
if (senseGuid) {
for (const a in currentEntry.senses) {
if (currentEntry.senses.hasOwnProperty(a) && currentEntry.senses[a].guid === senseGuid) {
senseIndex = a;
senseIndex = +a;
if (exampleGuid) {
for (const b in currentEntry.senses[a].examples) {
if (currentEntry.senses[a].examples.hasOwnProperty(b) &&
currentEntry.senses[a].examples[b].guid === exampleGuid
) {
exampleIndex = b;
exampleIndex = +b;
}
}
}
Expand All @@ -885,29 +887,31 @@ export class LexiconEditorController implements angular.IController {
const examples = senses.fields.examples as LexConfigFieldList;
if (exampleGuid && exampleIndex) {
if (currentEntry.senses[senseIndex].examples[exampleIndex].hasOwnProperty(field)) {
currentField = currentEntry.senses[senseIndex].examples[exampleIndex][field];
currentField = currentEntry.senses[senseIndex].examples[exampleIndex][field as keyof LexExample];
if (examples.fields.hasOwnProperty(field)) {
fieldConfig = examples.fields[field];
}
}
} else if (senseGuid && senseIndex) {
if (currentEntry.senses[senseIndex].hasOwnProperty(field)) {
currentField = currentEntry.senses[senseIndex][field];
currentField = currentEntry.senses[senseIndex][field as keyof LexSense];
if (senses.fields.hasOwnProperty(field)) {
fieldConfig = senses.fields[field];
}
}
} else if (currentEntry.hasOwnProperty(field)) {
currentField = currentEntry[field];
currentField = currentEntry[field as keyof LexEntry];
if (this.lecConfig.entry.fields.hasOwnProperty(field)) {
fieldConfig = this.lecConfig.entry.fields[field];
}
}

if (currentField !== null) {
if (currentField.hasOwnProperty(inputSystem)) {
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
currentValue = currentField[inputSystem].value;
} else if (currentField.hasOwnProperty('value')) {
// @ts-expect-error Typescript should understand .hasOwnProperty, but doesn't
currentValue = currentField.value;
} else {
currentValue = optionKey;
Expand Down Expand Up @@ -956,9 +960,9 @@ export class LexiconEditorController implements angular.IController {
parts.field = field;
parts.fieldConfig = fieldConfig;
parts.inputSystem = inputSystem;
parts.sense.index = senseIndex;
parts.sense.index = ""+senseIndex;
parts.sense.guid = senseGuid;
parts.example.index = exampleIndex;
parts.example.index = ""+exampleIndex;
parts.example.guid = exampleGuid;
return parts;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class FieldTextController implements angular.IController {
this.fte.toolbar = '[[]]';
}

this.autocapitalize = autocapitalizeHints[this.fteFieldName] || 'none'
this.autocapitalize = autocapitalizeHints[this.fteFieldName as keyof typeof autocapitalizeHints] || 'none'
}

disabledMsg(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class LexiconAppController implements angular.IController {
if (rights.canEditProject()) {
this.lexProjectService.users().then(result => {
if (result.ok) {
const users = {};
const users: Record<string, User> = {};
for (const user of (result.data.users as User[])) {
users[user.id] = user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export class ConfigurationFieldUnifiedViewModel {
}
}

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

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

static checkIfAllRoleSelected(setting: SettingsBase, settings: SettingsBase[], selectAll: SettingsBase,
role: string): void {
role: RoleName): void {
ConfigurationFieldUnifiedViewModel.checkIfAllRowSelected(setting);
ConfigurationFieldUnifiedViewModel.checkIfAllRoleColumnSelected(settings, selectAll, role);
}
Expand Down Expand Up @@ -405,7 +405,7 @@ export class ConfigurationFieldUnifiedViewModel {
return tags;
}

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

export type RoleName = 'observer' | 'commenter' | 'contributor' | 'manager';
export class RoleType {
observer: string = 'observer';
commenter: string = 'observer_with_comment';
contributor: string = 'contributor';
manager: string = 'project_manager';

static roles(): string[] {
static roles(): RoleName[] {
return ['observer', 'commenter', 'contributor', 'manager'];
}
}
Expand Down
2 changes: 0 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"downlevelIteration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"ignoreDeprecations": "5.0",
"importHelpers": true,
"lib": ["es7", "dom"],
"module": "es6",
Expand All @@ -26,7 +25,6 @@
},
"rootDir": "src/angular-app",
"sourceMap": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5",
"typeRoots": ["node_modules/@types", "typings"]
},
Expand Down

0 comments on commit 9f51fab

Please sign in to comment.