Question
Suppress TypeScript Errors for One Line: TSLint vs TypeScript
Question
A jQuery plugin named Summernote adds a summernote property to the jQuery object. I do not have type definitions for this plugin, but I need to modify its configuration. TypeScript reports the following error:
Property
summernotedoes not exist on typejQueryStatic.
Why do /* tslint:disable */ and /* tslint:enable */ not suppress this error, and how can I handle this error for only these lines?
(function ($) {
/* tslint:disable */
delete $.summernote.options.keyMap.pc.TAB;
delete $.summernote.options.keyMap.mac.TAB;
/* tslint:enable */
})(jQuery);
The project uses TypeScript with noImplicitAny: true.
Short Answer
You will learn the difference between a TSLint rule violation and a TypeScript compiler type error. You will also learn when to use @ts-ignore, a local any cast, or a proper declaration to work with JavaScript libraries that attach properties dynamically.
Concept
TSLint and TypeScript are separate tools.
- TSLint checks style and configurable lint rules, such as naming conventions or quote style.
- TypeScript checks whether code is type-safe and whether properties exist on known types.
This error is produced by the TypeScript compiler, not by TSLint:
Property 'summernote' does not exist on type 'jQueryStatic'.
Therefore, this comment only affects TSLint and cannot hide the compiler error:
/* tslint:disable */
TypeScript knows that $ is a jQueryStatic object. Its installed jQuery type declarations do not say that jQueryStatic has a summernote property, even though the Summernote plugin adds that property when the application runs.
The best long-term fix is to describe the plugin with a type declaration. When that is not practical, keep the unsafe part small by casting only the plugin access to any.
Mental Model
Think of TypeScript as a building inspector and TSLint as a writing-style editor.
- The style editor can ignore a formatting rule when you add a TSLint disable comment.
- The building inspector still rejects a plan that references a room that is not on the blueprint.
Summernote creates a real room at runtime, but the jQuery TypeScript blueprint does not include it. You can either update the blueprint with a declaration or temporarily tell TypeScript, “I know this particular value has an unknown shape.”
Syntax and Examples
A TSLint suppression affects only TSLint:
// tslint:disable-next-line:no-console
console.log('debug message');
A TypeScript suppression affects the next line only:
// @ts-ignore
const pluginOptions = $.summernote.options;
For this situation, a local cast is usually clearer than ignoring an entire line:
(function ($) {
const summernote = ($ as any).summernote;
delete summernote.options.keyMap.pc.TAB;
delete summernote.options.keyMap.mac.TAB;
})(jQuery);
$ as any tells TypeScript not to type-check the summernote property lookup. The rest of the program remains type-checked.
If you use modern TypeScript and expect an error to remain intentionally, prefer @ts-expect-error:
// @ts-expect-error Summernote is loaded globally and has no installed type declaration.
const summernote = $.summernote;
Unlike @ts-ignore, @ts-expect-error reports an error if the next line stops producing a TypeScript error. This helps reveal obsolete suppressions.
Step by Step Execution
Consider this code:
(function ($) {
const summernote = ($ as any).summernote;
delete summernote.options.keyMap.pc.TAB;
delete summernote.options.keyMap.mac.TAB;
})(jQuery);
Step by step:
jQueryis passed into the immediately invoked function and becomes the local$parameter.- TypeScript considers
$ajQueryStaticvalue. ($ as any)creates a type-checking escape hatch for this expression only.- Reading
.summernoteis allowed because values of typeanypermit arbitrary property access. - The result is stored in
summernoteand is also treated asany. - Both
deleteoperations run normally in JavaScript and remove theTABproperties if that object path exists at runtime.
The cast does not change the generated JavaScript. It only changes what TypeScript permits during compilation.
Real World Use Cases
This pattern appears whenever a JavaScript library changes an object dynamically but its TypeScript declarations do not describe that change.
- Legacy jQuery plugins: A plugin adds
$.pluginNameor$('#id').pluginName(). - Browser globals: A script loaded from a CDN adds
window.analyticsorwindow.SomeSDK. - Application configuration: A server injects a configuration object onto
window. - Third-party integrations: An older payment, charting, or editor library has incomplete types.
- Gradual migration: A JavaScript codebase is being moved to TypeScript one file at a time.
In each case, start with a narrow cast or temporary suppression, then add types when the integration becomes important or widely used.
Real Codebase Usage
In production code, developers usually choose one of these patterns.
1. Narrow local cast for a small one-off use
const summernote = ($ as any).summernote;
delete summernote.options.keyMap.pc.TAB;
This is appropriate when the unknown API is used in one small location.
2. Runtime guard before accessing optional globals
const summernote = ($ as any).summernote;
if (summernote?.options?.keyMap) {
delete summernote.options.keyMap.pc?.TAB;
delete summernote.options.keyMap.mac?.TAB;
}
A type cast only satisfies the compiler; it does not guarantee that the plugin was loaded. A guard protects against runtime failures when load order is uncertain.
3. Add a minimal declaration for repeated usage
declare global {
interface JQueryStatic {
summernote: any;
}
}
export {};
Put this in a project declaration file such as src/types/summernote.d.ts. Now $.summernote is accepted throughout the project. You can replace any with a real interface later.
4. Install or write proper type definitions
If reliable type declarations are available for a library, use them. This provides autocomplete, catches misspellings, and documents the integration. Avoid declaring a library as any across a whole application when only one property is missing.
Common Mistakes
Expecting tslint:disable to hide compiler errors
This does not work for TypeScript errors:
/* tslint:disable */
delete $.summernote.options.keyMap.pc.TAB;
/* tslint:enable */
Avoid it: Use @ts-ignore or @ts-expect-error for a one-line compiler suppression, or use a local cast/declaration.
Casting the entire jQuery object unnecessarily
const unsafeJQuery = $ as any;
unsafeJQuery.summernote.options.keyMap.pc.TAB;
This works, but it makes every later use of unsafeJQuery untyped.
Prefer: Cast only where the missing property is accessed.
const summernote = ($ as any).summernote;
Using @ts-ignore without documenting why
// @ts-ignore
delete $.summernote.options.keyMap.pc.TAB;
This hides every TypeScript error on the next line, including unrelated future errors.
Prefer: Use a narrow cast, or explain the exception.
// @ts-expect-error Summernote is provided by a legacy global plugin.
delete $.summernote.options.keyMap.pc.TAB;
Forgetting runtime safety
const summernote = ($ as any).summernote;
delete summernote.options.keyMap.pc.TAB;
This can still throw if Summernote has not loaded.
Avoid it: Ensure the plugin loads first, or check that the expected objects exist before modifying them.
Assuming noImplicitAny creates this error
noImplicitAny reports places where TypeScript would infer any implicitly. The summernote error instead occurs because the known JQueryStatic type does not declare that property.
Comparisons
| Approach | Scope | Best use | Trade-off |
|---|---|---|---|
/* tslint:disable */ | TSLint rules | Style or lint-rule exceptions | Does not suppress TypeScript compiler errors |
// @ts-ignore | Next TypeScript line | Short-lived exception | Can hide any error on that line |
// @ts-expect-error | Next TypeScript line | Expected, documented type error | Requires TypeScript 3.9 or later |
($ as any).summernote | One expression | Small untyped integration | Loses checking after the cast |
interface JQueryStatic { summernote: ... } | Project-wide | Plugin used in several places | Requires maintaining a declaration |
| Official type definitions | Library-wide | Stable, commonly used API | May not exist or may be incomplete |
A local cast is often the practical middle ground: safer than making all jQuery usage any, but faster than fully modeling a legacy plugin.
Cheat Sheet
// TSLint only: does NOT affect TypeScript compiler errors
// tslint:disable-next-line:rule-name
// Suppress the next TypeScript error
// @ts-ignore
someCodeThatFailsTypeChecking();
// Prefer when the error is intentional and expected
// @ts-expect-error Explanation of the known missing type
someCodeThatFailsTypeChecking();
// Narrow escape hatch for an undeclared plugin property
const plugin = ($ as any).summernote;
// Declare a property added to jQuery by a plugin
interface JQueryStatic {
summernote: any;
}
Rules of thumb:
- A TSLint comment cannot suppress a TypeScript type error.
- Keep
anycasts as narrow and local as possible. - Use
@ts-expect-errorrather than@ts-ignorewhen available. - Add a declaration when the untyped API is used repeatedly.
- Type assertions do not perform runtime checks; verify plugins are loaded when necessary.
FAQ
Why does tslint:disable not suppress the summernote error?
Because the error is generated by the TypeScript compiler. TSLint comments only control TSLint rules.
How do I disable a TypeScript error for one line?
Place // @ts-ignore or, preferably, // @ts-expect-error directly above the line that produces the error.
Is @ts-ignore safe to use?
It is sometimes necessary, but it hides all TypeScript errors on the next line. Prefer a narrow cast or @ts-expect-error with a reason.
What does ($ as any) do in TypeScript?
It tells TypeScript to treat that expression as untyped. It does not convert or validate the value at runtime.
How can I type a jQuery plugin that adds $.summernote?
Use declaration merging to add summernote to JQueryStatic, ideally with a specific interface describing the plugin API.
Does allowJs: true make undeclared plugin properties valid?
No. It allows JavaScript files to be part of the TypeScript project, but it does not add missing properties to TypeScript type declarations.
Should I add summernote: any to JQueryStatic?
It is acceptable as a temporary or small-project solution. For frequent use, define the relevant options and methods with real types.
Mini Project
Description
Create a small TypeScript setup helper that customizes a globally loaded Summernote plugin without turning the entire jQuery object into any. The helper also verifies that the plugin exists before changing its key map.
Goal
Write a function that removes Summernote's TAB key mapping for both PC and Mac only when the plugin configuration is available.
Requirements
- Create a function that accepts a
JQueryStaticvalue. - Access the undeclared
summernoteproperty with a narrow type escape hatch. - Do not throw if Summernote or its key map is unavailable.
- Remove
TABfrom both the PC and Mac key maps when they exist. - Call the function with
jQuery.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.