Skip to content

fix: stricter linting, improved handling of plugin errrors #795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
node_modules/
coverage/
lib/
e2e/
example/


**/*.config.js
.eslintrc.js
constants-generator.js
38 changes: 37 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
module.exports = {
extends: ['@react-native-community', 'prettier'],
extends: [
'@react-native-community',
'prettier',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
plugins: ['@typescript-eslint'],
rules: {
'prettier/prettier': [
'error',
Expand All @@ -11,13 +17,43 @@ module.exports = {
useTabs: false,
},
],
"no-void": [
"error",
{ "allowAsStatement": true }
],
"@typescript-eslint/restrict-template-expressions": ["warn", {
allowNumber: true,
allowBoolean: true,
allowAny: true,
allowNullish: true
}],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/strict-boolean-expressions": "error"
},
overrides: [
// Detox tests
{
files: ['*.e2e.js'],
env: {
jest: true,
},
},
// Jest
{
files: ['**/__tests__/**', '**/__mocks__/**'],
plugins: ['jest'],

rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/unbound-method": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/ban-ts-comment": "warn"
},
},
],
parserOptions: {
project: ['./tsconfig.json'],
},
};
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ jobs:
cache: 'yarn'
- name: Install
run: yarn install --frozen-lockfile
- name: Lint
run: yarn lint
- name: Build
run: yarn build
# Linter has to run after the build because it relies on TS types
- name: Lint
run: yarn lint
- name: Test
run: yarn test --coverage

Expand Down
23 changes: 5 additions & 18 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import 'react-native-gesture-handler';
import * as React from 'react';
import RNBootSplash from 'react-native-bootsplash';
Expand All @@ -20,28 +21,14 @@ import { useState } from 'react';
import { Logger } from './plugins/Logger';

//To see an example Consent Manager uncomment the following
//@ts-ignore
// import { ConsentManager } from './plugins/ConsentManager';

// @ts-ignore
import { AmplitudeSessionPlugin } from '@segment/analytics-react-native-plugin-amplitude-session';
// import { ConsentManager } from './plugins/ConsentManager';
// import { FirebasePlugin } from '@segment/analytics-react-native-plugin-firebase';

// @ts-ignore
// import { FacebookAppEventsPlugin } from '@segment/analytics-react-native-plugin-facebook-app-events';

// @ts-ignore
// import { IdfaPlugin } from '@segment/analytics-react-native-plugin-idfa';

// @ts-ignore
import { AmplitudeSessionPlugin } from '@segment/analytics-react-native-plugin-amplitude-session';

//@ts-ignore
// import { AdvertisingIdPlugin } from '@segment/analytics-react-native-plugin-advertising-id';

//@ts-ignore
// import { ClevertapPlugin } from '@segment/analytics-react-native-plugin-clevertap';

//@ts-ignore
// import { BrazePlugin } from '@segment/analytics-react-native-plugin-braze';

const segmentClient = createClient({
Expand Down Expand Up @@ -130,7 +117,7 @@ const getActiveRouteName = (

const App = () => {
React.useEffect(() => {
RNBootSplash.hide();
void RNBootSplash.hide();
}, []);

const [routeName, setRouteName] = useState('Unknown');
Expand All @@ -142,7 +129,7 @@ const App = () => {
const newRouteName = getActiveRouteName(state);

if (routeName !== newRouteName) {
segmentClient.screen(newRouteName);
void segmentClient.screen(newRouteName);

setRouteName(newRouteName);
}
Expand Down
33 changes: 24 additions & 9 deletions example/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useAnalytics } from '@segment/analytics-react-native';

const screenWidth = Dimensions.get('screen').width;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Home = ({ navigation }: { navigation: any }) => {
const { screen, track, identify, group, alias, reset, flush } =
useAnalytics();
Expand All @@ -24,36 +25,40 @@ const Home = ({ navigation }: { navigation: any }) => {
name: 'Track',
testID: 'BUTTON_TRACK',
onPress: () => {
track('Track pressed', { foo: 'bar' });
void track('Track pressed', { foo: 'bar' });
},
},
{
color: colors.darkGreen,
name: 'Screen',
testID: 'BUTTON_SCREEN',
onPress: () => {
screen('Home Screen', { foo: 'bar' });
void screen('Home Screen', { foo: 'bar' });
},
},
{
color: colors.purple,
name: 'Identify',
testID: 'BUTTON_IDENTIFY',
onPress: () => {
identify('user_2', { username: 'simplyTheBest' });
void identify('user_2', { username: 'simplyTheBest' });
},
},
{
color: colors.lightPurple,
name: 'Group',
testID: 'BUTTON_GROUP',
onPress: () => group('best-group', { companyId: 'Lala' }),
onPress: () => {
void group('best-group', { companyId: 'Lala' });
},
},
{
color: colors.indigo,
name: 'Alias',
testID: 'BUTTON_ALIAS',
onPress: () => alias('new-id'),
onPress: () => {
void alias('new-id');
},
},
];
}, []);
Expand All @@ -63,13 +68,17 @@ const Home = ({ navigation }: { navigation: any }) => {
color: colors.pink,
name: 'Flush',
testID: 'BUTTON_FLUSH',
onPress: () => flush(),
onPress: () => {
void flush();
},
},
{
color: colors.orange,
name: 'Reset',
testID: 'BUTTON_RESET',
onPress: () => reset(),
onPress: () => {
void reset();
},
},
];

Expand Down Expand Up @@ -111,7 +120,10 @@ const Home = ({ navigation }: { navigation: any }) => {
styles.button,
{ backgroundColor: colors.purple, width: screenWidth / 2 - 40 },
]}
onPress={() => navigation.navigate('SecondPage')}
onPress={() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
navigation.navigate('SecondPage');
}}
>
<Text style={styles.text}>Page</Text>
</TouchableOpacity>
Expand All @@ -120,7 +132,10 @@ const Home = ({ navigation }: { navigation: any }) => {
styles.button,
{ backgroundColor: colors.acai, width: screenWidth / 2 - 40 },
]}
onPress={() => navigation.navigate('Modal')}
onPress={() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
navigation.navigate('Modal');
}}
>
<Text style={styles.text}>Modal</Text>
</TouchableOpacity>
Expand Down
4 changes: 2 additions & 2 deletions example/src/plugins/ConsentManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class ConsentManager extends Plugin {
if (status === true) {
this.consentStatus = true;
this.sendQueued();
this.analytics?.track('Consent Authorized');
void this.analytics?.track('Consent Authorized');
}
if (status === false) {
this.queuedEvents = [];
Expand All @@ -68,7 +68,7 @@ export class ConsentManager extends Plugin {

sendQueued() {
this.queuedEvents.forEach((event) => {
this.analytics?.process(event);
void this.analytics?.process(event);
});
this.queuedEvents = [];
}
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
"@changesets/cli": "^2.16.0",
"@commitlint/config-conventional": "^16.2.4",
"@react-native-community/eslint-config": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"commitlint": "^16.2.4",
"eslint": "^7.2.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^3.1.3",
"husky": "^8.0.0",
"jest": "^27.5.1",
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"semantic-release-monorepo": "^7.0.5",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4",
"type-fest": "^3.6.1",
"@react-native-async-storage/async-storage": "^1.0"
},
"react-native-builder-bob": {
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/__mocks__/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export const Platform = {
};

export class NativeEventEmitter {
constructor() {}
addListener = () => jest.fn();
removeListener = () => jest.fn();
removeAllListeners = () => jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { DestinationPlugin } from '../../plugin';

export const getMockDestinationPlugin = () => {
const destinationPlugin = new DestinationPlugin();
destinationPlugin.flush = jest.fn() as jest.MockedFunction<any>;
destinationPlugin.flush = jest.fn() as jest.MockedFunction<never>;
return destinationPlugin;
};
8 changes: 6 additions & 2 deletions packages/core/src/__tests__/__helpers__/mockEventStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ export class MockEventStore {

constructor(initialData?: SegmentEvent[]) {
this.events = [...(initialData ?? [])];
this.initialData = JSON.parse(JSON.stringify(initialData ?? []));
this.initialData = JSON.parse(
JSON.stringify(initialData ?? [])
) as SegmentEvent[];
}

reset = () => {
this.events = JSON.parse(JSON.stringify(this.initialData));
this.events = JSON.parse(
JSON.stringify(this.initialData)
) as SegmentEvent[];
};

getState = createMockStoreGetter(() => ({ events: this.events }));
Expand Down
14 changes: 9 additions & 5 deletions packages/core/src/__tests__/__helpers__/mockSegmentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ export class MockSegmentStore implements Storage {
private initialData: StoreData;

reset = () => {
this.data = JSON.parse(JSON.stringify(this.initialData));
this.data = JSON.parse(JSON.stringify(this.initialData)) as StoreData;
};

constructor(initialData?: Partial<StoreData>) {
this.data = { ...INITIAL_VALUES, ...initialData };
this.initialData = JSON.parse(
JSON.stringify({ ...INITIAL_VALUES, ...initialData })
);
) as StoreData;
}

private callbacks = {
Expand All @@ -81,7 +81,9 @@ export class MockSegmentStore implements Storage {
return this.data.isReady;
}),
onChange: (_callback: (value: boolean) => void) => {
return () => {};
return () => {
return;
};
},
};

Expand All @@ -102,7 +104,7 @@ export class MockSegmentStore implements Storage {

readonly settings: Watchable<SegmentAPIIntegrations | undefined> &
Settable<SegmentAPIIntegrations> &
Dictionary<string, IntegrationSettings> = {
Dictionary<string, IntegrationSettings, SegmentAPIIntegrations> = {
get: createMockStoreGetter(() => this.data.settings),
onChange: (callback: (value?: SegmentAPIIntegrations) => void) =>
this.callbacks.settings.register(callback),
Expand All @@ -117,12 +119,13 @@ export class MockSegmentStore implements Storage {
add: (key: string, value: IntegrationSettings) => {
this.data.settings[key] = value;
this.callbacks.settings.run(this.data.settings);
return Promise.resolve(this.data.settings);
},
};

readonly filters: Watchable<DestinationFilters | undefined> &
Settable<DestinationFilters> &
Dictionary<string, RoutingRule> = {
Dictionary<string, RoutingRule, DestinationFilters> = {
get: createMockStoreGetter(() => this.data.filters),
onChange: (callback: (value?: DestinationFilters) => void) =>
this.callbacks.filters.register(callback),
Expand All @@ -137,6 +140,7 @@ export class MockSegmentStore implements Storage {
add: (key: string, value: RoutingRule) => {
this.data.filters[key] = value;
this.callbacks.filters.run(this.data.filters);
return Promise.resolve(this.data.filters);
},
};

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/__tests__/__helpers__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ jest.mock('react-native');
jest.mock('uuid');
jest.mock('react-native-get-random-values');
jest.mock('@react-native-async-storage/async-storage', () =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
8 changes: 4 additions & 4 deletions packages/core/src/__tests__/__helpers__/setupSegmentClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ export const createTestClient = (
class ObservablePlugin extends UtilityPlugin {
type = PluginType.after;

override execute(
execute = async (
event: SegmentEvent
): SegmentEvent | Promise<SegmentEvent | undefined> | undefined {
super.execute(event);
): Promise<SegmentEvent | undefined> => {
await super.execute(event);
return event;
}
};
}

const mockPlugin = new ObservablePlugin();
Expand Down
Loading